···
+
inherit (lib.trivial) isFloat isInt isBool;
+
inherit (lib.modules) mkIf;
+
concatMapAttrsStringSep
+
inherit (lib.lists) all isList flatten;
+
inherit (lib.generators) toKeyValue;
+
# Deeply checks types for a given type function. Calls `override` with type and value.
+
check = value: prev.check value && (override type value);
+
listOf' = deep types.listOf (type: value: all type.check value);
+
attrsOf' = deep types.attrsOf (type: value: all (item: type.check item.value) (attrsToList value));
+
listOfAtom = listOf' atom;
+
atomOrList = with types; either atom listOfAtom;
+
lists = listOf' atomOrList;
+
kvPair = attrsOf' atomOrList;
+
kvPairs = listOf' kvPair;
+
# Options that eval to a string with a header (foo:key=value)
+
headerKvPair = attrsOf' (attrsOf' atomOrList);
+
headerKvPairs = attrsOf' (listOf' (attrsOf' atomOrList));
+
# Toplevel config type.
+
description = "Kismet config stanza";
+
invalid = atom: throw "invalid value '${toString atom}' of type '${typeOf atom}'";
+
if hasInfix "\"" atom || hasInfix "," atom then
+
''"${replaceStrings [ ''"'' ] [ ''\"'' ] atom}"''
+
else if isFloat atom || isInt atom || isBool atom then
+
# Converts an inline atom or list to a string.
+
if isList atomOrList then
+
mkAtom "${concatMapStringsSep "," mkAtom atomOrList}"
+
# Converts an out of line atom or list to a string.
+
if isList atomOrList then
+
"${concatMapStringsSep "," mkAtomOrListInline atomOrList}"
+
# Throws if the string matches the given regex.
+
assert (match regex str) == null;
+
# Converts a set of k/v pairs.
+
convertKv = concatMapAttrsStringSep "," (
+
name: value: "${mkAtom (deny "=" name)}=${mkAtomOrListInline value}"
+
# Converts k/v pairs with a header.
+
convertKvWithHeader = header: attrs: "${mkAtom (deny ":" header)}:${convertKv attrs}";
+
# Converts the entire config.
+
convertConfig = mapAttrs' (
+
# Convert foo' into 'foo+' for support for '+=' syntax.
+
newName = if hasSuffix "'" name then substring 0 (stringLength name - 1) name + "+" else name;
+
# Get the stringified value.
+
if headerKvPairs.check value then
+
mapAttrsToList (header: values: (map (value: convertKvWithHeader header value) values)) value
+
else if headerKvPair.check value then
+
mapAttrsToList convertKvWithHeader value
+
else if kvPairs.check value then
+
else if kvPair.check value then
+
else if listOfAtom.check value then
+
else if lists.check value then
+
else if atom.check value then
+
nameValuePair newName newValue
+
(toKeyValue { listsAsDuplicateKeys = true; }) (
+
filterAttrs (_: value: value != null) (convertConfig options)
+
cfg = config.services.kismet;
+
options.services.kismet = {
+
enable = mkEnableOption "kismet";
+
package = mkPackageOption pkgs "kismet" { };
+
description = "The user to run Kismet as.";
+
description = "The group to run Kismet as.";
+
serverName = mkOption {
+
description = "The name of the server.";
+
serverDescription = mkOption {
+
description = "The description of the server.";
+
default = "NixOS Kismet server";
+
description = "The log types.";
+
type = with types; listOf str;
+
default = [ "kismet" ];
+
description = "The Kismet data directory.";
+
default = "/var/lib/kismet";
+
description = "True to enable the HTTP server.";
+
description = "The address to listen on. Note that this cannot be a hostname or Kismet will not start.";
+
description = "The port to listen on.";
+
Options for Kismet. See:
+
https://www.kismetwireless.net/docs/readme/configuring/configfiles/
+
type = with types; attrsOf topLevel;
+
example = literalExpression ''
+
/* Examples for atoms */
+
# dot11_link_bssts=false
+
dot11_link_bssts = false; # Boolean
+
# dot11_related_bss_window=10000000
+
dot11_related_bss_window = 10000000; # Integer
+
# devicefound=00:11:22:33:44:55
+
devicefound = "00:11:22:33:44:55"; # String
+
log_types' = "wiglecsv";
+
/* Examples for lists of atoms */
+
# wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
+
wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
+
# alert=ADHOCCONFLICT,5/min,1/sec
+
# alert=ADVCRYPTCHANGE,5/min,1/sec
+
[ "ADHOCCONFLICT" "5/min" "1/sec" ]
+
[ "ADVCRYPTCHANGE" "5/min" "1/sec" ]
+
/* Examples for sets of atoms */
+
# source=wlan0:name=ath11k
+
source.wlan0 = { name = "ath11k"; };
+
/* Examples with colon-suffixed headers */
+
# gps=gpsd:host=localhost,port=2947
+
# apspoof=Foo1:ssid=Bar1,validmacs="00:11:22:33:44:55,aa:bb:cc:dd:ee:ff"
+
# apspoof=Foo1:ssid=Bar2,validmacs="01:12:23:34:45:56,ab:bc:cd:de:ef:f0"
+
# apspoof=Foo2:ssid=Baz1,validmacs="11:22:33:44:55:66,bb:cc:dd:ee:ff:00"
+
{ ssid = "Bar1"; validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ]; }
+
{ ssid = "Bar2"; validmacs = [ "01:12:23:34:45:56" "ab:bc:cd:de:ef:f0" ]; }
+
# because Foo1 is a list, Foo2 needs to be as well
+
validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ];
+
extraConfig = mkOption {
+
Literal Kismet config lines appended to the site config.
+
Note that `services.kismet.settings` allows you to define
+
all options here using Nix attribute sets.
+
# Looks like the following in `services.kismet.settings`:
+
# wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
+
wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
+
configDir = "${cfg.dataDir}/.kismet";
+
server_name = cfg.serverName;
+
server_description = cfg.serverDescription;
+
logging_enabled = cfg.logTypes != [ ];
+
log_types = cfg.logTypes;
+
// optionalAttrs cfg.httpd.enable {
+
httpd_bind_address = cfg.httpd.address;
+
httpd_port = cfg.httpd.port;
+
httpd_auth_file = "${configDir}/kismet_httpd.conf";
+
httpd_home = "${cfg.package}/share/kismet/httpd";
+
systemd.tmpfiles.settings = {
+
inherit (cfg) user group;
+
inherit (cfg) user group;
+
systemd.services.kismet =
+
kismetConf = pkgs.writeText "kismet.conf" ''
+
${mkKismetConf settings}
+
description = "Kismet monitoring service";
+
wants = [ "basic.target" ];
+
wantedBy = [ "multi-user.target" ];
+
kismetPreStart = pkgs.writeShellScript "kismet-pre-start" ''
+
owner=${escapeShellArg "${cfg.user}:${cfg.group}"}
+
# Ensure permissions on directories Kismet uses.
+
chown "$owner" ~/ ~/.kismet
+
if [ -d "$package/etc" ]; then
+
for file in "$package/etc"/*.conf; do
+
# Symlink the config files if they exist or are already a link.
+
if [ ! -f "$base" ] || [ -L "$base" ]; then
+
for file in kismet_httpd.conf; do
+
# Un-symlink these files.
+
if [ -L "$file" ]; then
+
# Link the site config.
+
ln -sf ${kismetConf} kismet_site.conf
+
ExecStart = escapeShellArgs [
+
"${cfg.package}/bin/kismet"
+
"${configDir}/kismet.conf"
+
WorkingDirectory = cfg.dataDir;
+
ExecStartPre = "+${kismetPreStart}";
+
KillMode = "control-group";
+
CapabilityBoundingSet = capabilities;
+
AmbientCapabilities = capabilities;
+
LockPersonality = true;
+
NoNewPrivileges = true;
+
PrivateDevices = false;
+
ProtectControlGroups = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectProc = "invisible";
+
ProtectSystem = "full";
+
RestrictNamespaces = true;
+
RestrictSUIDSGID = true;
+
# Allow it to restart if the wifi interface is not up
+
unitConfig.StartLimitIntervalSec = 5;
+
users.groups.${cfg.group} = { };
+
users.users.${cfg.user} = {
+
description = "User for running Kismet";
+
meta.maintainers = with lib.maintainers; [ numinit ];