···
9
+
inherit (lib.trivial) isFloat isInt isBool;
10
+
inherit (lib.modules) mkIf;
11
+
inherit (lib.options)
17
+
inherit (lib.strings)
22
+
concatMapAttrsStringSep
31
+
inherit (lib.lists) all isList flatten;
32
+
inherit (lib.attrsets)
40
+
inherit (lib.generators) toKeyValue;
41
+
inherit (lib) types;
43
+
# Deeply checks types for a given type function. Calls `override` with type and value.
45
+
func: override: type:
51
+
check = value: prev.check value && (override type value);
55
+
listOf' = deep types.listOf (type: value: all type.check value);
58
+
attrsOf' = deep types.attrsOf (type: value: all (item: type.check item.value) (attrsToList value));
60
+
# Kismet config atoms.
70
+
listOfAtom = listOf' atom;
71
+
atomOrList = with types; either atom listOfAtom;
72
+
lists = listOf' atomOrList;
73
+
kvPair = attrsOf' atomOrList;
74
+
kvPairs = listOf' kvPair;
76
+
# Options that eval to a string with a header (foo:key=value)
77
+
headerKvPair = attrsOf' (attrsOf' atomOrList);
78
+
headerKvPairs = attrsOf' (listOf' (attrsOf' atomOrList));
80
+
# Toplevel config type.
97
+
description = "Kismet config stanza";
101
+
invalid = atom: throw "invalid value '${toString atom}' of type '${typeOf atom}'";
103
+
# Converts an atom.
106
+
if isString atom then
107
+
if hasInfix "\"" atom || hasInfix "," atom then
108
+
''"${replaceStrings [ ''"'' ] [ ''\"'' ] atom}"''
111
+
else if isFloat atom || isInt atom || isBool atom then
116
+
# Converts an inline atom or list to a string.
117
+
mkAtomOrListInline =
119
+
if isList atomOrList then
120
+
mkAtom "${concatMapStringsSep "," mkAtom atomOrList}"
124
+
# Converts an out of line atom or list to a string.
127
+
if isList atomOrList then
128
+
"${concatMapStringsSep "," mkAtomOrListInline atomOrList}"
132
+
# Throws if the string matches the given regex.
135
+
assert (match regex str) == null;
138
+
# Converts a set of k/v pairs.
139
+
convertKv = concatMapAttrsStringSep "," (
140
+
name: value: "${mkAtom (deny "=" name)}=${mkAtomOrListInline value}"
143
+
# Converts k/v pairs with a header.
144
+
convertKvWithHeader = header: attrs: "${mkAtom (deny ":" header)}:${convertKv attrs}";
146
+
# Converts the entire config.
147
+
convertConfig = mapAttrs' (
150
+
# Convert foo' into 'foo+' for support for '+=' syntax.
151
+
newName = if hasSuffix "'" name then substring 0 (stringLength name - 1) name + "+" else name;
153
+
# Get the stringified value.
155
+
if headerKvPairs.check value then
157
+
mapAttrsToList (header: values: (map (value: convertKvWithHeader header value) values)) value
159
+
else if headerKvPair.check value then
160
+
mapAttrsToList convertKvWithHeader value
161
+
else if kvPairs.check value then
162
+
map convertKv value
163
+
else if kvPair.check value then
165
+
else if listOfAtom.check value then
167
+
else if lists.check value then
168
+
map mkAtomOrList value
169
+
else if atom.check value then
174
+
nameValuePair newName newValue
179
+
(toKeyValue { listsAsDuplicateKeys = true; }) (
180
+
filterAttrs (_: value: value != null) (convertConfig options)
183
+
cfg = config.services.kismet;
186
+
options.services.kismet = {
187
+
enable = mkEnableOption "kismet";
188
+
package = mkPackageOption pkgs "kismet" { };
190
+
description = "The user to run Kismet as.";
192
+
default = "kismet";
195
+
description = "The group to run Kismet as.";
197
+
default = "kismet";
199
+
serverName = mkOption {
200
+
description = "The name of the server.";
202
+
default = "Kismet";
204
+
serverDescription = mkOption {
205
+
description = "The description of the server.";
207
+
default = "NixOS Kismet server";
209
+
logTypes = mkOption {
210
+
description = "The log types.";
211
+
type = with types; listOf str;
212
+
default = [ "kismet" ];
214
+
dataDir = mkOption {
215
+
description = "The Kismet data directory.";
217
+
default = "/var/lib/kismet";
220
+
enable = mkOption {
221
+
description = "True to enable the HTTP server.";
225
+
address = mkOption {
226
+
description = "The address to listen on. Note that this cannot be a hostname or Kismet will not start.";
228
+
default = "127.0.0.1";
231
+
description = "The port to listen on.";
236
+
settings = mkOption {
238
+
Options for Kismet. See:
239
+
https://www.kismetwireless.net/docs/readme/configuring/configfiles/
242
+
type = with types; attrsOf topLevel;
243
+
example = literalExpression ''
245
+
/* Examples for atoms */
246
+
# dot11_link_bssts=false
247
+
dot11_link_bssts = false; # Boolean
249
+
# dot11_related_bss_window=10000000
250
+
dot11_related_bss_window = 10000000; # Integer
252
+
# devicefound=00:11:22:33:44:55
253
+
devicefound = "00:11:22:33:44:55"; # String
255
+
# log_types+=wiglecsv
256
+
log_types' = "wiglecsv";
258
+
/* Examples for lists of atoms */
259
+
# wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
260
+
wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
262
+
# alert=ADHOCCONFLICT,5/min,1/sec
263
+
# alert=ADVCRYPTCHANGE,5/min,1/sec
265
+
[ "ADHOCCONFLICT" "5/min" "1/sec" ]
266
+
[ "ADVCRYPTCHANGE" "5/min" "1/sec" ]
269
+
/* Examples for sets of atoms */
270
+
# source=wlan0:name=ath11k
271
+
source.wlan0 = { name = "ath11k"; };
273
+
/* Examples with colon-suffixed headers */
274
+
# gps=gpsd:host=localhost,port=2947
276
+
host = "localhost";
280
+
# apspoof=Foo1:ssid=Bar1,validmacs="00:11:22:33:44:55,aa:bb:cc:dd:ee:ff"
281
+
# apspoof=Foo1:ssid=Bar2,validmacs="01:12:23:34:45:56,ab:bc:cd:de:ef:f0"
282
+
# apspoof=Foo2:ssid=Baz1,validmacs="11:22:33:44:55:66,bb:cc:dd:ee:ff:00"
284
+
{ ssid = "Bar1"; validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ]; }
285
+
{ ssid = "Bar2"; validmacs = [ "01:12:23:34:45:56" "ab:bc:cd:de:ef:f0" ]; }
288
+
# because Foo1 is a list, Foo2 needs to be as well
292
+
validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ];
298
+
extraConfig = mkOption {
300
+
Literal Kismet config lines appended to the site config.
301
+
Note that `services.kismet.settings` allows you to define
302
+
all options here using Nix attribute sets.
307
+
# Looks like the following in `services.kismet.settings`:
308
+
# wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
309
+
wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
316
+
configDir = "${cfg.dataDir}/.kismet";
320
+
server_name = cfg.serverName;
321
+
server_description = cfg.serverDescription;
322
+
logging_enabled = cfg.logTypes != [ ];
323
+
log_types = cfg.logTypes;
325
+
// optionalAttrs cfg.httpd.enable {
326
+
httpd_bind_address = cfg.httpd.address;
327
+
httpd_port = cfg.httpd.port;
328
+
httpd_auth_file = "${configDir}/kismet_httpd.conf";
329
+
httpd_home = "${cfg.package}/share/kismet/httpd";
333
+
systemd.tmpfiles.settings = {
337
+
inherit (cfg) user group;
343
+
inherit (cfg) user group;
349
+
systemd.services.kismet =
351
+
kismetConf = pkgs.writeText "kismet.conf" ''
352
+
${mkKismetConf settings}
357
+
description = "Kismet monitoring service";
358
+
wants = [ "basic.target" ];
363
+
wantedBy = [ "multi-user.target" ];
370
+
kismetPreStart = pkgs.writeShellScript "kismet-pre-start" ''
371
+
owner=${escapeShellArg "${cfg.user}:${cfg.group}"}
374
+
# Ensure permissions on directories Kismet uses.
375
+
chown "$owner" ~/ ~/.kismet
378
+
package=${cfg.package}
379
+
if [ -d "$package/etc" ]; then
380
+
for file in "$package/etc"/*.conf; do
381
+
# Symlink the config files if they exist or are already a link.
382
+
base="''${file##*/}"
383
+
if [ ! -f "$base" ] || [ -L "$base" ]; then
384
+
ln -sf "$file" "$base"
389
+
for file in kismet_httpd.conf; do
390
+
# Un-symlink these files.
391
+
if [ -L "$file" ]; then
392
+
cp "$file" ".$file"
394
+
mv ".$file" "$file"
396
+
chown "$owner" "$file"
400
+
# Link the site config.
401
+
ln -sf ${kismetConf} kismet_site.conf
406
+
ExecStart = escapeShellArgs [
407
+
"${cfg.package}/bin/kismet"
413
+
"${cfg.package}/share"
416
+
"${configDir}/kismet.conf"
418
+
WorkingDirectory = cfg.dataDir;
419
+
ExecStartPre = "+${kismetPreStart}";
420
+
Restart = "always";
421
+
KillMode = "control-group";
422
+
CapabilityBoundingSet = capabilities;
423
+
AmbientCapabilities = capabilities;
424
+
LockPersonality = true;
425
+
NoNewPrivileges = true;
426
+
PrivateDevices = false;
428
+
PrivateUsers = false;
429
+
ProtectClock = true;
430
+
ProtectControlGroups = true;
431
+
ProtectHome = true;
432
+
ProtectHostname = true;
433
+
ProtectKernelLogs = true;
434
+
ProtectKernelModules = true;
435
+
ProtectKernelTunables = true;
436
+
ProtectProc = "invisible";
437
+
ProtectSystem = "full";
438
+
RestrictNamespaces = true;
439
+
RestrictSUIDSGID = true;
443
+
TimeoutStopSec = 30;
446
+
# Allow it to restart if the wifi interface is not up
447
+
unitConfig.StartLimitIntervalSec = 5;
449
+
users.groups.${cfg.group} = { };
450
+
users.users.${cfg.user} = {
451
+
inherit (cfg) group;
452
+
description = "User for running Kismet";
453
+
isSystemUser = true;
454
+
home = cfg.dataDir;
458
+
meta.maintainers = with lib.maintainers; [ numinit ];