1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.pcscd;
cfgFile = pkgs.writeText "reader.conf" (
builtins.concatStringsSep "\n\n" config.services.pcscd.readerConfigs
);
pluginEnv = pkgs.buildEnv {
name = "pcscd-plugins";
paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
};
in
{
imports = [
(lib.mkChangedOptionModule
[ "services" "pcscd" "readerConfig" ]
[ "services" "pcscd" "readerConfigs" ]
(
config:
let
readerConfig = lib.getAttrFromPath [ "services" "pcscd" "readerConfig" ] config;
in
[ readerConfig ]
)
)
];
options.services.pcscd = {
enable = lib.mkEnableOption "PCSC-Lite daemon, to access smart cards using SCard API (PC/SC)";
package = (lib.mkPackageOption pkgs "pcsclite" { }) // {
default = if config.security.polkit.enable then pkgs.pcscliteWithPolkit else pkgs.pcsclite;
defaultText = lib.literalExpression "if config.security.polkit.enable then pkgs.pcscliteWithPolkit else pkgs.pcsclite";
};
plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
defaultText = lib.literalExpression "[ pkgs.ccid ]";
example = lib.literalExpression "[ pkgs.pcsc-cyberjack ]";
description = "Plugin packages to be used for PCSC-Lite.";
};
readerConfigs = lib.mkOption {
type = lib.types.listOf lib.types.lines;
default = [ ];
example = [
''
FRIENDLYNAME "Some serial reader"
DEVICENAME /dev/ttyS0
LIBPATH /path/to/serial_reader.so
CHANNELID 1
''
];
description = ''
Configuration for devices that aren't hotpluggable.
See {manpage}`reader.conf(5)` for valid options.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line arguments to be passed to the PCSC daemon.";
};
ignoreReaderNames = lib.mkOption {
type = lib.types.listOf (lib.types.strMatching "[^:]+");
default = [ ];
description = ''
List of reader name patterns for the PCSC daemon to ignore.
For more precise control, readers can be ignored through udev rules
(cf. {option}`services.udev.extraRules`) by setting the
`PCSCLITE_IGNORE` property, for example:
```
ACTION!="remove|unbind", SUBSYSTEM=="usb", ATTR{idVendor}=="20a0", ENV{PCSCLITE_IGNORE}="1"
```
'';
example = [
"Nitrokey"
"YubiKey"
];
};
extendReaderNames = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
String to append to every reader name. The special variable `$HOSTNAME`
will be expanded to the current host name.
'';
example = " $HOSTNAME";
};
};
config = lib.mkIf config.services.pcscd.enable {
environment.etc."reader.conf".source = cfgFile;
environment.systemPackages = [ cfg.package ];
systemd.packages = [ cfg.package ];
services.pcscd.plugins = [ pkgs.ccid ];
services.udev.packages = [ pkgs.ccid ];
systemd.sockets.pcscd.wantedBy = [ "sockets.target" ];
systemd.services.pcscd = {
environment = {
PCSCLITE_HP_DROPDIR = pluginEnv;
PCSCLITE_FILTER_IGNORE_READER_NAMES = lib.mkIf (cfg.ignoreReaderNames != [ ]) (
lib.concatStringsSep ":" cfg.ignoreReaderNames
);
PCSCLITE_FILTER_EXTEND_READER_NAMES = lib.mkIf (
cfg.extendReaderNames != null
) cfg.extendReaderNames;
};
# If the cfgFile is empty and not specified (in which case the default
# /etc/reader.conf is assumed), pcscd will happily start going through the
# entire confdir (/etc in our case) looking for a config file and try to
# parse everything it finds. Doesn't take a lot of imagination to see how
# well that works. It really shouldn't do that to begin with, but to work
# around it, we force the path to the cfgFile.
#
# https://github.com/NixOS/nixpkgs/issues/121088
serviceConfig.ExecStart = [
""
"${lib.getExe cfg.package} -f -x -c ${cfgFile} ${lib.escapeShellArgs cfg.extraArgs}"
];
};
users.users.pcscd = {
isSystemUser = true;
group = "pcscd";
};
users.groups.pcscd = { };
};
}
|