1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.privoxy;
8
9 serialise = name: val:
10 if isList val then concatMapStrings (serialise name) val
11 else if isBool val then serialise name (if val then "1" else "0")
12 else "${name} ${toString val}\n";
13
14 configType = with types;
15 let atom = oneOf [ int bool string path ];
16 in attrsOf (either atom (listOf atom))
17 // { description = ''
18 privoxy configuration type. The format consists of an attribute
19 set of settings. Each setting can be either a value (integer, string,
20 boolean or path) or a list of such values.
21 '';
22 };
23
24 ageType = types.str // {
25 check = x:
26 isString x &&
27 (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null);
28 description = "tmpfiles.d(5) age format";
29 };
30
31 configFile = pkgs.writeText "privoxy.conf"
32 (concatStrings (
33 # Relative paths in some options are relative to confdir. Privoxy seems
34 # to parse the options in order of appearance, so this must come first.
35 # Nix however doesn't preserve the order in attrsets, so we have to
36 # hardcode confdir here.
37 [ "confdir ${pkgs.privoxy}/etc\n" ]
38 ++ mapAttrsToList serialise cfg.settings
39 ));
40
41 inspectAction = pkgs.writeText "inspect-all-https.action"
42 ''
43 # Enable HTTPS inspection for all requests
44 {+https-inspection}
45 /
46 '';
47
48in
49
50{
51
52 ###### interface
53
54 options.services.privoxy = {
55
56 enable = mkEnableOption "Privoxy, non-caching filtering proxy";
57
58 enableTor = mkOption {
59 type = types.bool;
60 default = false;
61 description = ''
62 Whether to configure Privoxy to use Tor's faster SOCKS port,
63 suitable for HTTP.
64 '';
65 };
66
67 inspectHttps = mkOption {
68 type = types.bool;
69 default = false;
70 description = ''
71 Whether to configure Privoxy to inspect HTTPS requests, meaning all
72 encrypted traffic will be filtered as well. This works by decrypting
73 and re-encrypting the requests using a per-domain generated certificate.
74
75 To issue per-domain certificates, Privoxy must be provided with a CA
76 certificate, using the <literal>ca-cert-file</literal>,
77 <literal>ca-key-file</literal> settings.
78
79 <warning><para>
80 The CA certificate must also be added to the system trust roots,
81 otherwise browsers will reject all Privoxy certificates as invalid.
82 You can do so by using the option
83 <option>security.pki.certificateFiles</option>.
84 </para></warning>
85 '';
86 };
87
88 certsLifetime = mkOption {
89 type = ageType;
90 default = "10d";
91 example = "12h";
92 description = ''
93 If <literal>inspectHttps</literal> is enabled, the time generated HTTPS
94 certificates will be stored in a temporary directory for reuse. Once
95 the lifetime has expired the directory will cleared and the certificate
96 will have to be generated again, on-demand.
97
98 Depending on the traffic, you may want to reduce the lifetime to limit
99 the disk usage, since Privoxy itself never deletes the certificates.
100
101 <note><para>The format is that of the <literal>tmpfiles.d(5)</literal>
102 Age parameter.</para></note>
103 '';
104 };
105
106 userActions = mkOption {
107 type = types.lines;
108 default = "";
109 description = ''
110 Actions to be included in a <literal>user.action</literal> file. This
111 will have a higher priority and can be used to override all other
112 actions.
113 '';
114 };
115
116 userFilters = mkOption {
117 type = types.lines;
118 default = "";
119 description = ''
120 Filters to be included in a <literal>user.filter</literal> file. This
121 will have a higher priority and can be used to override all other
122 filters definitions.
123 '';
124 };
125
126 settings = mkOption {
127 type = types.submodule {
128 freeformType = configType;
129
130 options.listen-address = mkOption {
131 type = types.str;
132 default = "127.0.0.1:8118";
133 description = "Pair of address:port the proxy server is listening to.";
134 };
135
136 options.enable-edit-actions = mkOption {
137 type = types.bool;
138 default = false;
139 description = "Whether the web-based actions file editor may be used.";
140 };
141
142 options.actionsfile = mkOption {
143 type = types.listOf types.str;
144 # This must come after all other entries, in order to override the
145 # other actions/filters installed by Privoxy or the user.
146 apply = x: x ++ optional (cfg.userActions != "")
147 (toString (pkgs.writeText "user.actions" cfg.userActions));
148 default = [ "match-all.action" "default.action" ];
149 description = ''
150 List of paths to Privoxy action files. These paths may either be
151 absolute or relative to the privoxy configuration directory.
152 '';
153 };
154
155 options.filterfile = mkOption {
156 type = types.listOf types.str;
157 default = [ "default.filter" ];
158 apply = x: x ++ optional (cfg.userFilters != "")
159 (toString (pkgs.writeText "user.filter" cfg.userFilters));
160 description = ''
161 List of paths to Privoxy filter files. These paths may either be
162 absolute or relative to the privoxy configuration directory.
163 '';
164 };
165 };
166 default = {};
167 example = literalExample ''
168 { # Listen on IPv6 only
169 listen-address = "[::]:8118";
170
171 # Forward .onion requests to Tor
172 forward-socks5 = ".onion localhost:9050 .";
173
174 # Log redirects and filters
175 debug = [ 128 64 ];
176 # This is equivalent to writing these lines
177 # in the Privoxy configuration file:
178 # debug 128
179 # debug 64
180 }
181 '';
182 description = ''
183 This option is mapped to the main Privoxy configuration file.
184 Check out the Privoxy user manual at
185 <link xlink:href="https://www.privoxy.org/user-manual/config.html"/>
186 for available settings and documentation.
187
188 <note><para>
189 Repeated settings can be represented by using a list.
190 </para></note>
191 '';
192 };
193
194 };
195
196 ###### implementation
197
198 config = mkIf cfg.enable {
199
200 users.users.privoxy = {
201 description = "Privoxy daemon user";
202 isSystemUser = true;
203 group = "privoxy";
204 };
205
206 users.groups.privoxy = {};
207
208 systemd.tmpfiles.rules = optional cfg.inspectHttps
209 "d ${cfg.settings.certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}";
210
211 systemd.services.privoxy = {
212 description = "Filtering web proxy";
213 after = [ "network.target" "nss-lookup.target" ];
214 wantedBy = [ "multi-user.target" ];
215 serviceConfig = {
216 User = "privoxy";
217 Group = "privoxy";
218 ExecStart = "${pkgs.privoxy}/bin/privoxy --no-daemon ${configFile}";
219 PrivateDevices = true;
220 PrivateTmp = true;
221 ProtectHome = true;
222 ProtectSystem = "full";
223 };
224 unitConfig = mkIf cfg.inspectHttps {
225 ConditionPathExists = with cfg.settings;
226 [ ca-cert-file ca-key-file ];
227 };
228 };
229
230 services.tor.settings.SOCKSPort = mkIf cfg.enableTor [
231 # Route HTTP traffic over a faster port (without IsolateDestAddr).
232 { addr = "127.0.0.1"; port = 9063; IsolateDestAddr = false; }
233 ];
234
235 services.privoxy.settings = {
236 user-manual = "${pkgs.privoxy}/share/doc/privoxy/user-manual";
237 # This is needed for external filters
238 temporary-directory = "/tmp";
239 filterfile = [ "default.filter" ];
240 actionsfile =
241 [ "match-all.action"
242 "default.action"
243 ] ++ optional cfg.inspectHttps (toString inspectAction);
244 } // (optionalAttrs cfg.enableTor {
245 forward-socks5 = "/ 127.0.0.1:9063 .";
246 toggle = true;
247 enable-remote-toggle = false;
248 enable-edit-actions = false;
249 enable-remote-http-toggle = false;
250 }) // (optionalAttrs cfg.inspectHttps {
251 # This allows setting absolute key/crt paths
252 ca-directory = "/var/empty";
253 certificate-directory = "/run/privoxy/certs";
254 trusted-cas-file = "/etc/ssl/certs/ca-certificates.crt";
255 });
256
257 };
258
259 imports =
260 let
261 top = x: [ "services" "privoxy" x ];
262 setting = x: [ "services" "privoxy" "settings" x ];
263 in
264 [ (mkRenamedOptionModule (top "enableEditActions") (setting "enable-edit-actions"))
265 (mkRenamedOptionModule (top "listenAddress") (setting "listen-address"))
266 (mkRenamedOptionModule (top "actionsFiles") (setting "actionsfile"))
267 (mkRenamedOptionModule (top "filterFiles") (setting "filterfile"))
268 (mkRemovedOptionModule (top "extraConfig")
269 ''
270 Use services.privoxy.settings instead.
271 This is part of the general move to use structured settings instead of raw
272 text for config as introduced by RFC0042:
273 https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
274 '')
275 ];
276
277 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
278
279}