mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-22 17:31:04 +03:00
460 lines
13 KiB
Nix
460 lines
13 KiB
Nix
![]() |
{
|
||
|
config,
|
||
|
lib,
|
||
|
pkgs,
|
||
|
...
|
||
|
}:
|
||
|
|
||
|
let
|
||
|
inherit (lib.trivial) isFloat isInt isBool;
|
||
|
inherit (lib.modules) mkIf;
|
||
|
inherit (lib.options)
|
||
|
literalExpression
|
||
|
mkOption
|
||
|
mkPackageOption
|
||
|
mkEnableOption
|
||
|
;
|
||
|
inherit (lib.strings)
|
||
|
isString
|
||
|
escapeShellArg
|
||
|
escapeShellArgs
|
||
|
concatMapStringsSep
|
||
|
concatMapAttrsStringSep
|
||
|
replaceStrings
|
||
|
substring
|
||
|
stringLength
|
||
|
hasInfix
|
||
|
hasSuffix
|
||
|
typeOf
|
||
|
match
|
||
|
;
|
||
|
inherit (lib.lists) all isList flatten;
|
||
|
inherit (lib.attrsets)
|
||
|
attrsToList
|
||
|
filterAttrs
|
||
|
optionalAttrs
|
||
|
mapAttrs'
|
||
|
mapAttrsToList
|
||
|
nameValuePair
|
||
|
;
|
||
|
inherit (lib.generators) toKeyValue;
|
||
|
inherit (lib) types;
|
||
|
|
||
|
# Deeply checks types for a given type function. Calls `override` with type and value.
|
||
|
deep =
|
||
|
func: override: type:
|
||
|
let
|
||
|
prev = func type;
|
||
|
in
|
||
|
prev
|
||
|
// {
|
||
|
check = value: prev.check value && (override type value);
|
||
|
};
|
||
|
|
||
|
# Deep listOf.
|
||
|
listOf' = deep types.listOf (type: value: all type.check value);
|
||
|
|
||
|
# Deep attrsOf.
|
||
|
attrsOf' = deep types.attrsOf (type: value: all (item: type.check item.value) (attrsToList value));
|
||
|
|
||
|
# Kismet config atoms.
|
||
|
atom =
|
||
|
with types;
|
||
|
oneOf [
|
||
|
number
|
||
|
bool
|
||
|
str
|
||
|
];
|
||
|
|
||
|
# Composite types.
|
||
|
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.
|
||
|
topLevel =
|
||
|
let
|
||
|
topLevel' =
|
||
|
with types;
|
||
|
oneOf [
|
||
|
headerKvPairs
|
||
|
headerKvPair
|
||
|
kvPairs
|
||
|
kvPair
|
||
|
listOfAtom
|
||
|
lists
|
||
|
atom
|
||
|
];
|
||
|
in
|
||
|
topLevel'
|
||
|
// {
|
||
|
description = "Kismet config stanza";
|
||
|
};
|
||
|
|
||
|
# Throws invalid.
|
||
|
invalid = atom: throw "invalid value '${toString atom}' of type '${typeOf atom}'";
|
||
|
|
||
|
# Converts an atom.
|
||
|
mkAtom =
|
||
|
atom:
|
||
|
if isString atom then
|
||
|
if hasInfix "\"" atom || hasInfix "," atom then
|
||
|
''"${replaceStrings [ ''"'' ] [ ''\"'' ] atom}"''
|
||
|
else
|
||
|
atom
|
||
|
else if isFloat atom || isInt atom || isBool atom then
|
||
|
toString atom
|
||
|
else
|
||
|
invalid atom;
|
||
|
|
||
|
# Converts an inline atom or list to a string.
|
||
|
mkAtomOrListInline =
|
||
|
atomOrList:
|
||
|
if isList atomOrList then
|
||
|
mkAtom "${concatMapStringsSep "," mkAtom atomOrList}"
|
||
|
else
|
||
|
mkAtom atomOrList;
|
||
|
|
||
|
# Converts an out of line atom or list to a string.
|
||
|
mkAtomOrList =
|
||
|
atomOrList:
|
||
|
if isList atomOrList then
|
||
|
"${concatMapStringsSep "," mkAtomOrListInline atomOrList}"
|
||
|
else
|
||
|
mkAtom atomOrList;
|
||
|
|
||
|
# Throws if the string matches the given regex.
|
||
|
deny =
|
||
|
regex: str:
|
||
|
assert (match regex str) == null;
|
||
|
str;
|
||
|
|
||
|
# 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' (
|
||
|
name: value:
|
||
|
let
|
||
|
# 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.
|
||
|
newValue =
|
||
|
if headerKvPairs.check value then
|
||
|
flatten (
|
||
|
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
|
||
|
map convertKv value
|
||
|
else if kvPair.check value then
|
||
|
convertKv value
|
||
|
else if listOfAtom.check value then
|
||
|
mkAtomOrList value
|
||
|
else if lists.check value then
|
||
|
map mkAtomOrList value
|
||
|
else if atom.check value then
|
||
|
mkAtom value
|
||
|
else
|
||
|
invalid value;
|
||
|
in
|
||
|
nameValuePair newName newValue
|
||
|
);
|
||
|
|
||
|
mkKismetConf =
|
||
|
options:
|
||
|
(toKeyValue { listsAsDuplicateKeys = true; }) (
|
||
|
filterAttrs (_: value: value != null) (convertConfig options)
|
||
|
);
|
||
|
|
||
|
cfg = config.services.kismet;
|
||
|
in
|
||
|
{
|
||
|
options.services.kismet = {
|
||
|
enable = mkEnableOption "kismet";
|
||
|
package = mkPackageOption pkgs "kismet" { };
|
||
|
user = mkOption {
|
||
|
description = "The user to run Kismet as.";
|
||
|
type = types.str;
|
||
|
default = "kismet";
|
||
|
};
|
||
|
group = mkOption {
|
||
|
description = "The group to run Kismet as.";
|
||
|
type = types.str;
|
||
|
default = "kismet";
|
||
|
};
|
||
|
serverName = mkOption {
|
||
|
description = "The name of the server.";
|
||
|
type = types.str;
|
||
|
default = "Kismet";
|
||
|
};
|
||
|
serverDescription = mkOption {
|
||
|
description = "The description of the server.";
|
||
|
type = types.str;
|
||
|
default = "NixOS Kismet server";
|
||
|
};
|
||
|
logTypes = mkOption {
|
||
|
description = "The log types.";
|
||
|
type = with types; listOf str;
|
||
|
default = [ "kismet" ];
|
||
|
};
|
||
|
dataDir = mkOption {
|
||
|
description = "The Kismet data directory.";
|
||
|
type = types.path;
|
||
|
default = "/var/lib/kismet";
|
||
|
};
|
||
|
httpd = {
|
||
|
enable = mkOption {
|
||
|
description = "True to enable the HTTP server.";
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
};
|
||
|
address = mkOption {
|
||
|
description = "The address to listen on. Note that this cannot be a hostname or Kismet will not start.";
|
||
|
type = types.str;
|
||
|
default = "127.0.0.1";
|
||
|
};
|
||
|
port = mkOption {
|
||
|
description = "The port to listen on.";
|
||
|
type = types.port;
|
||
|
default = 2501;
|
||
|
};
|
||
|
};
|
||
|
settings = mkOption {
|
||
|
description = ''
|
||
|
Options for Kismet. See:
|
||
|
https://www.kismetwireless.net/docs/readme/configuring/configfiles/
|
||
|
'';
|
||
|
default = { };
|
||
|
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
|
||
|
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
|
||
|
alert = [
|
||
|
[ "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
|
||
|
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"
|
||
|
apspoof.Foo1 = [
|
||
|
{ 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
|
||
|
apspoof.Foo2 = [
|
||
|
{
|
||
|
ssid = "Bar2";
|
||
|
validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ];
|
||
|
};
|
||
|
];
|
||
|
}
|
||
|
'';
|
||
|
};
|
||
|
extraConfig = mkOption {
|
||
|
description = ''
|
||
|
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.
|
||
|
'';
|
||
|
default = "";
|
||
|
type = types.str;
|
||
|
example = ''
|
||
|
# Looks like the following in `services.kismet.settings`:
|
||
|
# wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
|
||
|
wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
config =
|
||
|
let
|
||
|
configDir = "${cfg.dataDir}/.kismet";
|
||
|
settings =
|
||
|
cfg.settings
|
||
|
// {
|
||
|
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";
|
||
|
};
|
||
|
in
|
||
|
mkIf cfg.enable {
|
||
|
systemd.tmpfiles.settings = {
|
||
|
"10-kismet" = {
|
||
|
${cfg.dataDir} = {
|
||
|
d = {
|
||
|
inherit (cfg) user group;
|
||
|
mode = "0750";
|
||
|
};
|
||
|
};
|
||
|
${configDir} = {
|
||
|
d = {
|
||
|
inherit (cfg) user group;
|
||
|
mode = "0750";
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
systemd.services.kismet =
|
||
|
let
|
||
|
kismetConf = pkgs.writeText "kismet.conf" ''
|
||
|
${mkKismetConf settings}
|
||
|
${cfg.extraConfig}
|
||
|
'';
|
||
|
in
|
||
|
{
|
||
|
description = "Kismet monitoring service";
|
||
|
wants = [ "basic.target" ];
|
||
|
after = [
|
||
|
"basic.target"
|
||
|
"network.target"
|
||
|
];
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
serviceConfig =
|
||
|
let
|
||
|
capabilities = [
|
||
|
"CAP_NET_ADMIN"
|
||
|
"CAP_NET_RAW"
|
||
|
];
|
||
|
kismetPreStart = pkgs.writeShellScript "kismet-pre-start" ''
|
||
|
owner=${escapeShellArg "${cfg.user}:${cfg.group}"}
|
||
|
mkdir -p ~/.kismet
|
||
|
|
||
|
# Ensure permissions on directories Kismet uses.
|
||
|
chown "$owner" ~/ ~/.kismet
|
||
|
cd ~/.kismet
|
||
|
|
||
|
package=${cfg.package}
|
||
|
if [ -d "$package/etc" ]; then
|
||
|
for file in "$package/etc"/*.conf; do
|
||
|
# Symlink the config files if they exist or are already a link.
|
||
|
base="''${file##*/}"
|
||
|
if [ ! -f "$base" ] || [ -L "$base" ]; then
|
||
|
ln -sf "$file" "$base"
|
||
|
fi
|
||
|
done
|
||
|
fi
|
||
|
|
||
|
for file in kismet_httpd.conf; do
|
||
|
# Un-symlink these files.
|
||
|
if [ -L "$file" ]; then
|
||
|
cp "$file" ".$file"
|
||
|
rm -f "$file"
|
||
|
mv ".$file" "$file"
|
||
|
chmod 0640 "$file"
|
||
|
chown "$owner" "$file"
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Link the site config.
|
||
|
ln -sf ${kismetConf} kismet_site.conf
|
||
|
'';
|
||
|
in
|
||
|
{
|
||
|
Type = "simple";
|
||
|
ExecStart = escapeShellArgs [
|
||
|
"${cfg.package}/bin/kismet"
|
||
|
"--homedir"
|
||
|
cfg.dataDir
|
||
|
"--confdir"
|
||
|
configDir
|
||
|
"--datadir"
|
||
|
"${cfg.package}/share"
|
||
|
"--no-ncurses"
|
||
|
"-f"
|
||
|
"${configDir}/kismet.conf"
|
||
|
];
|
||
|
WorkingDirectory = cfg.dataDir;
|
||
|
ExecStartPre = "+${kismetPreStart}";
|
||
|
Restart = "always";
|
||
|
KillMode = "control-group";
|
||
|
CapabilityBoundingSet = capabilities;
|
||
|
AmbientCapabilities = capabilities;
|
||
|
LockPersonality = true;
|
||
|
NoNewPrivileges = true;
|
||
|
PrivateDevices = false;
|
||
|
PrivateTmp = true;
|
||
|
PrivateUsers = false;
|
||
|
ProtectClock = true;
|
||
|
ProtectControlGroups = true;
|
||
|
ProtectHome = true;
|
||
|
ProtectHostname = true;
|
||
|
ProtectKernelLogs = true;
|
||
|
ProtectKernelModules = true;
|
||
|
ProtectKernelTunables = true;
|
||
|
ProtectProc = "invisible";
|
||
|
ProtectSystem = "full";
|
||
|
RestrictNamespaces = true;
|
||
|
RestrictSUIDSGID = true;
|
||
|
User = cfg.user;
|
||
|
Group = cfg.group;
|
||
|
UMask = "0007";
|
||
|
TimeoutStopSec = 30;
|
||
|
};
|
||
|
|
||
|
# Allow it to restart if the wifi interface is not up
|
||
|
unitConfig.StartLimitIntervalSec = 5;
|
||
|
};
|
||
|
users.groups.${cfg.group} = { };
|
||
|
users.users.${cfg.user} = {
|
||
|
inherit (cfg) group;
|
||
|
description = "User for running Kismet";
|
||
|
isSystemUser = true;
|
||
|
home = cfg.dataDir;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
meta.maintainers = with lib.maintainers; [ numinit ];
|
||
|
}
|