-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstubby-zig.zig
More file actions
389 lines (315 loc) · 15.1 KB
/
stubby-zig.zig
File metadata and controls
389 lines (315 loc) · 15.1 KB
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
const std = @import("std");
const builtin = @import("builtin");
const MAX_INDEX = 99;
fn getEnv(allocator: std.mem.Allocator, env_map: *const std.process.EnvMap, key: []const u8, default_value: []const u8) ![]const u8 {
if (env_map.get(key)) |val| {
if (val.len > 0) {
return try allocator.dupe(u8, val);
}
}
return try allocator.dupe(u8, default_value);
}
fn buildList(allocator: std.mem.Allocator, env_map: *const std.process.EnvMap, prefix: []const u8, default_first: []const u8) ![]const u8 {
var list: std.ArrayList(u8) = .{};
defer list.deinit(allocator);
var i: u32 = 0;
var first = true;
while (i <= MAX_INDEX) : (i += 1) {
var key_buf: [256]u8 = undefined;
const key = try std.fmt.bufPrint(&key_buf, "{s}__{d}", .{ prefix, i });
var val: []const u8 = "";
if (env_map.get(key)) |env_val| {
if (env_val.len > 0) {
val = env_val;
}
}
if (i == 0 and val.len == 0 and default_first.len > 0) {
val = default_first;
}
if (val.len > 0) {
if (!first) {
try list.writer(allocator).writeAll("\n");
}
try list.writer(allocator).print("- {s}", .{val});
first = false;
}
}
if (first and default_first.len > 0) {
try list.writer(allocator).print("- {s}", .{default_first});
}
return try list.toOwnedSlice(allocator);
}
fn buildUpstreamList(allocator: std.mem.Allocator, env_map: *const std.process.EnvMap) ![]const u8 {
var list: std.ArrayList(u8) = .{};
defer list.deinit(allocator);
var i: u32 = 0;
var first = true;
while (i <= MAX_INDEX) : (i += 1) {
var address_key_buf: [256]u8 = undefined;
const address_key = try std.fmt.bufPrint(&address_key_buf, "STUBBY__UPSTREAM_RECURSIVE_SERVERS__{d}__ADDRESS_DATA", .{i});
var address_data: []const u8 = "";
if (env_map.get(address_key)) |env_val| {
if (env_val.len > 0) {
address_data = env_val;
}
}
if (i == 0 and address_data.len == 0) {
address_data = "194.242.2.2";
}
if (address_data.len > 0) {
if (!first) {
try list.writer(allocator).writeAll("\n");
}
try list.writer(allocator).print("- address_data: {s}", .{address_data});
var tls_port_key_buf: [256]u8 = undefined;
const tls_port_key = try std.fmt.bufPrint(&tls_port_key_buf, "STUBBY__UPSTREAM_RECURSIVE_SERVERS__{d}__TLS_PORT", .{i});
var tls_port: []const u8 = "";
if (env_map.get(tls_port_key)) |env_val| {
if (env_val.len > 0) {
tls_port = env_val;
}
}
if (tls_port.len > 0) {
try list.writer(allocator).print("\n tls_port: {s}", .{tls_port});
} else if (i == 0) {
try list.writer(allocator).writeAll("\n tls_port: 853");
}
var tls_auth_key_buf: [256]u8 = undefined;
const tls_auth_key = try std.fmt.bufPrint(&tls_auth_key_buf, "STUBBY__UPSTREAM_RECURSIVE_SERVERS__{d}__TLS_AUTH_NAME", .{i});
var tls_auth: []const u8 = "";
if (env_map.get(tls_auth_key)) |env_val| {
if (env_val.len > 0) {
tls_auth = env_val;
}
}
if (tls_auth.len > 0) {
try list.writer(allocator).print("\n tls_auth_name: \"{s}\"", .{tls_auth});
} else if (i == 0) {
try list.writer(allocator).writeAll("\n tls_auth_name: \"dns.mullvad.net\"");
}
first = false;
}
}
if (first) {
try list.writer(allocator).writeAll("- address_data: 194.242.2.2\n tls_port: 853\n tls_auth_name: \"dns.mullvad.net\"");
}
return try list.toOwnedSlice(allocator);
}
fn buildDNSSECTrustAnchors(allocator: std.mem.Allocator, env_map: *const std.process.EnvMap) ![]const u8 {
var list: std.ArrayList(u8) = .{};
defer list.deinit(allocator);
var i: u32 = 0;
var first = true;
while (i <= MAX_INDEX) : (i += 1) {
var key_buf: [256]u8 = undefined;
const key = try std.fmt.bufPrint(&key_buf, "STUBBY__DNSSEC_TRUST_ANCHORS__{d}", .{i});
if (env_map.get(key)) |val| {
if (val.len > 0) {
if (first) {
try list.writer(allocator).writeAll("dnssec_trust_anchors:\n");
first = false;
}
try list.writer(allocator).print(" - \"{s}\"\n", .{val});
}
}
}
return try list.toOwnedSlice(allocator);
}
fn replacePlaceholder(allocator: std.mem.Allocator, content: []const u8, placeholder: []const u8, replacement: []const u8) ![]u8 {
var result: std.ArrayList(u8) = .{};
defer result.deinit(allocator);
var i: usize = 0;
while (i < content.len) {
if (std.mem.indexOf(u8, content[i..], placeholder)) |pos| {
try result.writer(allocator).writeAll(content[i..][0..pos]);
try result.writer(allocator).writeAll(replacement);
i += pos + placeholder.len;
} else {
try result.writer(allocator).writeAll(content[i..]);
break;
}
}
return try result.toOwnedSlice(allocator);
}
fn replaceMultilinePlaceholder(allocator: std.mem.Allocator, content: []const u8, placeholder: []const u8, replacement: []const u8) ![]u8 {
var result: std.ArrayList(u8) = .{};
defer result.deinit(allocator);
var lines = std.mem.splitSequence(u8, content, "\n");
var first_line = true;
while (lines.next()) |line| {
if (std.mem.indexOf(u8, line, placeholder)) |_| {
var indent: usize = 0;
while (indent < line.len and (line[indent] == ' ' or line[indent] == '\t')) {
indent += 1;
}
var replacement_lines = std.mem.splitSequence(u8, replacement, "\n");
var first_replacement = true;
while (replacement_lines.next()) |repl_line| {
if (!first_line or !first_replacement) {
try result.writer(allocator).writeAll("\n");
}
if (repl_line.len > 0) {
var j: usize = 0;
while (j < indent) : (j += 1) {
try result.writer(allocator).writeAll(" ");
}
try result.writer(allocator).writeAll(repl_line);
}
first_replacement = false;
first_line = false;
}
} else {
if (!first_line) {
try result.writer(allocator).writeAll("\n");
}
try result.writer(allocator).writeAll(line);
first_line = false;
}
}
return try result.toOwnedSlice(allocator);
}
fn replaceDNSSECTrustAnchors(allocator: std.mem.Allocator, content: []const u8, trust_anchors: []const u8) ![]u8 {
const placeholder = "# __DNSSEC_TRUST_ANCHORS__";
if (trust_anchors.len == 0) {
var result: std.ArrayList(u8) = .{};
defer result.deinit(allocator);
var lines = std.mem.splitSequence(u8, content, "\n");
var first = true;
while (lines.next()) |line| {
if (std.mem.indexOf(u8, line, placeholder) == null) {
if (!first) {
try result.writer(allocator).writeAll("\n");
}
try result.writer(allocator).writeAll(line);
first = false;
}
}
return try result.toOwnedSlice(allocator);
} else {
return replacePlaceholder(allocator, content, placeholder, trust_anchors);
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var env = try std.process.getEnvMap(allocator);
defer env.deinit();
const template_path = try getEnv(allocator, &env, "TEMPLATE", "/etc/stubby/stubby.yml.template");
defer allocator.free(template_path);
const conf_path = try getEnv(allocator, &env, "CONF", "/etc/stubby/stubby.yml");
defer allocator.free(conf_path);
const template_content = try std.fs.cwd().readFileAlloc(allocator, template_path, 1024 * 1024);
defer allocator.free(template_content);
var content: []u8 = try allocator.dupe(u8, template_content);
defer allocator.free(content);
const log_level = try getEnv(allocator, &env, "STUBBY__LOG_LEVEL", "GETDNS_LOG_ERR");
defer allocator.free(log_level);
content = try replacePlaceholder(allocator, content, "__LOG_LEVEL__", log_level);
defer allocator.free(content);
const idle_timeout = try getEnv(allocator, &env, "STUBBY__IDLE_TIMEOUT", "10000");
defer allocator.free(idle_timeout);
content = try replacePlaceholder(allocator, content, "__IDLE_TIMEOUT__", idle_timeout);
defer allocator.free(content);
const edns_client_subnet_private = try getEnv(allocator, &env, "STUBBY__EDNS_CLIENT_SUBNET_PRIVATE", "1");
defer allocator.free(edns_client_subnet_private);
content = try replacePlaceholder(allocator, content, "__EDNS_CLIENT_SUBNET_PRIVATE__", edns_client_subnet_private);
defer allocator.free(content);
const round_robin_upstreams = try getEnv(allocator, &env, "STUBBY__ROUND_ROBIN_UPSTREAMS", "0");
defer allocator.free(round_robin_upstreams);
content = try replacePlaceholder(allocator, content, "__ROUND_ROBIN_UPSTREAMS__", round_robin_upstreams);
defer allocator.free(content);
const tls_authentication = try getEnv(allocator, &env, "STUBBY__TLS_AUTHENTICATION", "GETDNS_AUTHENTICATION_REQUIRED");
defer allocator.free(tls_authentication);
content = try replacePlaceholder(allocator, content, "__TLS_AUTHENTICATION__", tls_authentication);
defer allocator.free(content);
const tls_query_padding_blocksize = try getEnv(allocator, &env, "STUBBY__TLS_QUERY_PADDING_BLOCKSIZE", "256");
defer allocator.free(tls_query_padding_blocksize);
content = try replacePlaceholder(allocator, content, "__TLS_QUERY_PADDING_BLOCKSIZE__", tls_query_padding_blocksize);
defer allocator.free(content);
const tls_min_version = try getEnv(allocator, &env, "STUBBY__TLS_MIN_VERSION", "GETDNS_TLS1_2");
defer allocator.free(tls_min_version);
content = try replacePlaceholder(allocator, content, "__TLS_MIN_VERSION__", tls_min_version);
defer allocator.free(content);
const tls_max_version = try getEnv(allocator, &env, "STUBBY__TLS_MAX_VERSION", "GETDNS_TLS1_3");
defer allocator.free(tls_max_version);
content = try replacePlaceholder(allocator, content, "__TLS_MAX_VERSION__", tls_max_version);
defer allocator.free(content);
const dnssec = try getEnv(allocator, &env, "STUBBY__DNSSEC", "GETDNS_EXTENSION_TRUE");
defer allocator.free(dnssec);
content = try replacePlaceholder(allocator, content, "__DNSSEC__", dnssec);
defer allocator.free(content);
const dnssec_return_status = try getEnv(allocator, &env, "STUBBY__DNSSEC_RETURN_STATUS", "GETDNS_EXTENSION_TRUE");
defer allocator.free(dnssec_return_status);
content = try replacePlaceholder(allocator, content, "__DNSSEC_RETURN_STATUS__", dnssec_return_status);
defer allocator.free(content);
const dns_transport_list = try buildList(allocator, &env, "STUBBY__DNS_TRANSPORT_LIST", "GETDNS_TRANSPORT_TLS");
defer allocator.free(dns_transport_list);
content = try replaceMultilinePlaceholder(allocator, content, "__DNS_TRANSPORT_LIST__", dns_transport_list);
defer allocator.free(content);
const listen_addresses = try buildList(allocator, &env, "STUBBY__LISTEN_ADDRESSES", "0.0.0.0@8053");
defer allocator.free(listen_addresses);
content = try replaceMultilinePlaceholder(allocator, content, "__LISTEN_ADDRESSES__", listen_addresses);
defer allocator.free(content);
const upstream_recursive_servers = try buildUpstreamList(allocator, &env);
defer allocator.free(upstream_recursive_servers);
content = try replaceMultilinePlaceholder(allocator, content, "__UPSTREAM_RECURSIVE_SERVERS__", upstream_recursive_servers);
defer allocator.free(content);
const trust_anchors = try buildDNSSECTrustAnchors(allocator, &env);
defer allocator.free(trust_anchors);
content = try replaceDNSSECTrustAnchors(allocator, content, trust_anchors);
defer allocator.free(content);
const conf_dir = std.fs.path.dirname(conf_path) orelse "/etc/stubby";
try std.fs.cwd().makePath(conf_dir);
try std.fs.cwd().writeFile(.{ .sub_path = conf_path, .data = content });
const stubby_path = "/usr/bin/stubby";
const args = [_][]const u8{ stubby_path, "-C", conf_path, "-l" };
var env_strings: std.ArrayList(?[*:0]u8) = .{};
var env_slices: std.ArrayList([]u8) = .{};
defer {
for (env_slices.items) |slice| {
allocator.free(slice);
}
env_slices.deinit(allocator);
env_strings.deinit(allocator);
}
var env_iterator = env.iterator();
while (env_iterator.next()) |entry| {
const env_str_slice = try std.fmt.allocPrint(allocator, "{s}={s}", .{ entry.key_ptr.*, entry.value_ptr.* });
defer allocator.free(env_str_slice);
const env_str = try allocator.alloc(u8, env_str_slice.len + 1);
@memcpy(env_str[0..env_str_slice.len], env_str_slice);
env_str[env_str_slice.len] = 0;
const env_str_ptr: [*:0]u8 = @ptrCast(env_str.ptr);
try env_strings.append(allocator, env_str_ptr);
try env_slices.append(allocator, env_str);
}
try env_strings.append(allocator, null);
var args_z: std.ArrayList(?[*:0]const u8) = .{};
var args_slices: std.ArrayList([]u8) = .{};
defer {
for (args_slices.items) |slice| {
allocator.free(slice);
}
args_slices.deinit(allocator);
args_z.deinit(allocator);
}
for (args) |arg| {
const arg_slice = try allocator.alloc(u8, arg.len + 1);
@memcpy(arg_slice[0..arg.len], arg);
arg_slice[arg.len] = 0;
const arg_ptr: [*:0]const u8 = @ptrCast(arg_slice.ptr);
try args_z.append(allocator, arg_ptr);
try args_slices.append(allocator, arg_slice);
}
try args_z.append(allocator, null);
const path_slice = try allocator.alloc(u8, stubby_path.len + 1);
@memcpy(path_slice[0..stubby_path.len], stubby_path);
path_slice[stubby_path.len] = 0;
defer allocator.free(path_slice);
const path_ptr: [*:0]const u8 = @ptrCast(path_slice.ptr);
const args_ptr: [*:null]const ?[*:0]const u8 = @ptrCast(args_z.items.ptr);
const env_ptr: [*:null]const ?[*:0]u8 = @ptrCast(env_strings.items.ptr);
_ = std.os.linux.execve(path_ptr, args_ptr, env_ptr);
return error.ExecveFailed;
}