]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
9581bb84 | 2 | |
28e5e1e9 | 3 | #include "glyph-util.h" |
9581bb84 LP |
4 | #include "in-addr-util.h" |
5 | #include "resolved-dns-synthesize.h" | |
6 | #include "resolved-varlink.h" | |
7 | #include "socket-netlink.h" | |
8 | ||
9 | typedef struct LookupParameters { | |
10 | int ifindex; | |
11 | uint64_t flags; | |
12 | int family; | |
13 | union in_addr_union address; | |
14 | size_t address_size; | |
15 | char *name; | |
16 | } LookupParameters; | |
17 | ||
18 | static void lookup_parameters_destroy(LookupParameters *p) { | |
19 | assert(p); | |
20 | free(p->name); | |
21 | } | |
22 | ||
23 | static int reply_query_state(DnsQuery *q) { | |
24 | ||
25 | assert(q); | |
26 | assert(q->varlink_request); | |
27 | ||
28 | switch (q->state) { | |
29 | ||
30 | case DNS_TRANSACTION_NO_SERVERS: | |
31 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoNameServers", NULL); | |
32 | ||
33 | case DNS_TRANSACTION_TIMEOUT: | |
34 | return varlink_error(q->varlink_request, "io.systemd.Resolve.QueryTimedOut", NULL); | |
35 | ||
36 | case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: | |
37 | return varlink_error(q->varlink_request, "io.systemd.Resolve.MaxAttemptsReached", NULL); | |
38 | ||
39 | case DNS_TRANSACTION_INVALID_REPLY: | |
40 | return varlink_error(q->varlink_request, "io.systemd.Resolve.InvalidReply", NULL); | |
41 | ||
42 | case DNS_TRANSACTION_ERRNO: | |
43 | return varlink_error_errno(q->varlink_request, q->answer_errno); | |
44 | ||
45 | case DNS_TRANSACTION_ABORTED: | |
46 | return varlink_error(q->varlink_request, "io.systemd.Resolve.QueryAborted", NULL); | |
47 | ||
48 | case DNS_TRANSACTION_DNSSEC_FAILED: | |
49 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed", | |
50 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))))); | |
51 | ||
52 | case DNS_TRANSACTION_NO_TRUST_ANCHOR: | |
53 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL); | |
54 | ||
55 | case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: | |
56 | return varlink_error(q->varlink_request, "io.systemd.Resolve.ResourceRecordTypeUnsupported", NULL); | |
57 | ||
58 | case DNS_TRANSACTION_NETWORK_DOWN: | |
59 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL); | |
60 | ||
775ae354 LP |
61 | case DNS_TRANSACTION_NO_SOURCE: |
62 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoSource", NULL); | |
63 | ||
49ef064c LP |
64 | case DNS_TRANSACTION_STUB_LOOP: |
65 | return varlink_error(q->varlink_request, "io.systemd.Resolve.StubLoop", NULL); | |
66 | ||
9581bb84 LP |
67 | case DNS_TRANSACTION_NOT_FOUND: |
68 | /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we | |
69 | * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ | |
70 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", | |
71 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(DNS_RCODE_NXDOMAIN)))); | |
72 | ||
73 | case DNS_TRANSACTION_RCODE_FAILURE: | |
74 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", | |
75 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)))); | |
76 | ||
77 | case DNS_TRANSACTION_NULL: | |
78 | case DNS_TRANSACTION_PENDING: | |
79 | case DNS_TRANSACTION_VALIDATING: | |
80 | case DNS_TRANSACTION_SUCCESS: | |
81 | default: | |
04499a70 | 82 | assert_not_reached(); |
9581bb84 LP |
83 | } |
84 | } | |
85 | ||
86 | static void vl_on_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { | |
87 | DnsQuery *q; | |
88 | ||
89 | assert(s); | |
90 | assert(link); | |
91 | ||
92 | q = varlink_get_userdata(link); | |
93 | if (!q) | |
94 | return; | |
95 | ||
96 | if (!DNS_TRANSACTION_IS_LIVE(q->state)) | |
97 | return; | |
98 | ||
99 | log_debug("Client of active query vanished, aborting query."); | |
100 | dns_query_complete(q, DNS_TRANSACTION_ABORTED); | |
101 | } | |
102 | ||
cb456374 SK |
103 | static void vl_on_notification_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { |
104 | Manager *m = ASSERT_PTR(userdata); | |
105 | ||
106 | assert(s); | |
107 | assert(link); | |
108 | ||
109 | Varlink *removed_link = set_remove(m->varlink_subscription, link); | |
110 | if (removed_link) { | |
111 | varlink_unref(removed_link); | |
112 | log_debug("%u monitor clients remain active", set_size(m->varlink_subscription)); | |
113 | } | |
114 | } | |
115 | ||
3354f500 LP |
116 | static bool validate_and_mangle_flags( |
117 | const char *name, | |
118 | uint64_t *flags, | |
119 | uint64_t ok) { | |
120 | ||
9581bb84 LP |
121 | assert(flags); |
122 | ||
123 | /* This checks that the specified client-provided flags parameter actually makes sense, and mangles | |
124 | * it slightly. Specifically: | |
125 | * | |
775ae354 | 126 | * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the |
9581bb84 LP |
127 | * method-specific flags specified in 'ok'. |
128 | * | |
129 | * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled". | |
130 | * | |
131 | * The second rule means that clients can just pass 0 as flags for the common case, and all supported | |
132 | * protocols are enabled. Moreover it's useful so that client's do not have to be aware of all | |
133 | * protocols implemented in resolved, but can use 0 as protocols flags set as indicator for | |
134 | * "everything". | |
135 | */ | |
136 | ||
775ae354 LP |
137 | if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| |
138 | SD_RESOLVED_NO_CNAME| | |
139 | SD_RESOLVED_NO_VALIDATE| | |
140 | SD_RESOLVED_NO_SYNTHESIZE| | |
141 | SD_RESOLVED_NO_CACHE| | |
142 | SD_RESOLVED_NO_ZONE| | |
143 | SD_RESOLVED_NO_TRUST_ANCHOR| | |
144 | SD_RESOLVED_NO_NETWORK| | |
145 | ok)) | |
9581bb84 LP |
146 | return false; |
147 | ||
148 | if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ | |
149 | *flags |= SD_RESOLVED_PROTOCOLS_ALL; | |
150 | ||
3354f500 LP |
151 | /* If the SD_RESOLVED_NO_SEARCH flag is acceptable, and the query name is dot-suffixed, turn off |
152 | * search domains. Note that DNS name normalization drops the dot suffix, hence we propagate this | |
153 | * into the flags field as early as we can. */ | |
154 | if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0) | |
155 | *flags |= SD_RESOLVED_NO_SEARCH; | |
156 | ||
9581bb84 LP |
157 | return true; |
158 | } | |
159 | ||
c704288c | 160 | static void vl_method_resolve_hostname_complete(DnsQuery *query) { |
9581bb84 LP |
161 | _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; |
162 | _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; | |
c704288c | 163 | _cleanup_(dns_query_freep) DnsQuery *q = query; |
9581bb84 LP |
164 | _cleanup_free_ char *normalized = NULL; |
165 | DnsResourceRecord *rr; | |
166 | DnsQuestion *question; | |
167 | int ifindex, r; | |
168 | ||
169 | assert(q); | |
170 | ||
171 | if (q->state != DNS_TRANSACTION_SUCCESS) { | |
172 | r = reply_query_state(q); | |
173 | goto finish; | |
174 | } | |
175 | ||
1db8e6d1 | 176 | r = dns_query_process_cname_many(q); |
9581bb84 LP |
177 | if (r == -ELOOP) { |
178 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); | |
179 | goto finish; | |
180 | } | |
181 | if (r < 0) | |
182 | goto finish; | |
c704288c YW |
183 | if (r == DNS_QUERY_CNAME) { |
184 | /* This was a cname, and the query was restarted. */ | |
185 | TAKE_PTR(q); | |
9581bb84 | 186 | return; |
c704288c | 187 | } |
9581bb84 LP |
188 | |
189 | question = dns_query_question_for_protocol(q, q->answer_protocol); | |
190 | ||
191 | DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { | |
192 | _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; | |
193 | int family; | |
194 | const void *p; | |
195 | ||
196 | r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); | |
197 | if (r < 0) | |
198 | goto finish; | |
199 | if (r == 0) | |
200 | continue; | |
201 | ||
202 | if (rr->key->type == DNS_TYPE_A) { | |
203 | family = AF_INET; | |
204 | p = &rr->a.in_addr; | |
205 | } else if (rr->key->type == DNS_TYPE_AAAA) { | |
206 | family = AF_INET6; | |
207 | p = &rr->aaaa.in6_addr; | |
208 | } else { | |
209 | r = -EAFNOSUPPORT; | |
210 | goto finish; | |
211 | } | |
212 | ||
213 | r = json_build(&entry, | |
214 | JSON_BUILD_OBJECT( | |
f8f5b8d8 | 215 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), |
9581bb84 LP |
216 | JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)), |
217 | JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family))))); | |
218 | if (r < 0) | |
219 | goto finish; | |
220 | ||
221 | if (!canonical) | |
222 | canonical = dns_resource_record_ref(rr); | |
223 | ||
224 | r = json_variant_append_array(&array, entry); | |
225 | if (r < 0) | |
226 | goto finish; | |
227 | } | |
228 | ||
229 | if (json_variant_is_blank_object(array)) { | |
230 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
231 | goto finish; | |
232 | } | |
233 | ||
234 | assert(canonical); | |
235 | r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized); | |
236 | if (r < 0) | |
237 | goto finish; | |
238 | ||
239 | r = varlink_replyb(q->varlink_request, | |
240 | JSON_BUILD_OBJECT( | |
241 | JSON_BUILD_PAIR("addresses", JSON_BUILD_VARIANT(array)), | |
242 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)), | |
43fc4baa | 243 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); |
9581bb84 LP |
244 | finish: |
245 | if (r < 0) { | |
246 | log_error_errno(r, "Failed to send hostname reply: %m"); | |
247 | r = varlink_error_errno(q->varlink_request, r); | |
248 | } | |
9581bb84 LP |
249 | } |
250 | ||
251 | static int parse_as_address(Varlink *link, LookupParameters *p) { | |
252 | _cleanup_free_ char *canonical = NULL; | |
253 | int r, ff, parsed_ifindex, ifindex; | |
254 | union in_addr_union parsed; | |
255 | ||
256 | assert(link); | |
257 | assert(p); | |
258 | ||
259 | /* Check if this parses as literal address. If so, just parse it and return that, do not involve networking */ | |
260 | r = in_addr_ifindex_from_string_auto(p->name, &ff, &parsed, &parsed_ifindex); | |
261 | if (r < 0) | |
262 | return 0; /* not a literal address */ | |
263 | ||
264 | /* Make sure the data we parsed matches what is requested */ | |
265 | if ((p->family != AF_UNSPEC && ff != p->family) || | |
266 | (p->ifindex > 0 && parsed_ifindex > 0 && parsed_ifindex != p->ifindex)) | |
267 | return varlink_error(link, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
268 | ||
269 | ifindex = parsed_ifindex > 0 ? parsed_ifindex : p->ifindex; | |
270 | ||
271 | /* Reformat the address as string, to return as canonicalized name */ | |
272 | r = in_addr_ifindex_to_string(ff, &parsed, ifindex, &canonical); | |
273 | if (r < 0) | |
274 | return r; | |
275 | ||
276 | return varlink_replyb( | |
277 | link, | |
278 | JSON_BUILD_OBJECT( | |
279 | JSON_BUILD_PAIR("addresses", | |
280 | JSON_BUILD_ARRAY( | |
281 | JSON_BUILD_OBJECT( | |
282 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), | |
283 | JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(ff)), | |
284 | JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(&parsed, FAMILY_ADDRESS_SIZE(ff)))))), | |
285 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(canonical)), | |
5c1790d1 LP |
286 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(SD_RESOLVED_FLAGS_MAKE(dns_synthesize_protocol(p->flags), ff, true, true)| |
287 | SD_RESOLVED_SYNTHETIC)))); | |
9581bb84 LP |
288 | } |
289 | ||
290 | static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
291 | static const JsonDispatch dispatch_table[] = { | |
292 | { "ifindex", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, | |
293 | { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY }, | |
294 | { "family", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, family), 0 }, | |
295 | { "flags", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, | |
296 | {} | |
297 | }; | |
298 | ||
299 | _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; | |
300 | _cleanup_(lookup_parameters_destroy) LookupParameters p = { | |
301 | .family = AF_UNSPEC, | |
302 | }; | |
c704288c | 303 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
9807fdc1 | 304 | Manager *m; |
9581bb84 LP |
305 | int r; |
306 | ||
307 | assert(link); | |
9807fdc1 LP |
308 | |
309 | m = varlink_server_get_userdata(varlink_get_server(link)); | |
9581bb84 LP |
310 | assert(m); |
311 | ||
312 | if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) | |
313 | return -EINVAL; | |
314 | ||
315 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
316 | if (r < 0) | |
317 | return r; | |
318 | ||
319 | if (p.ifindex < 0) | |
320 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); | |
321 | ||
322 | r = dns_name_is_valid(p.name); | |
323 | if (r < 0) | |
324 | return r; | |
325 | if (r == 0) | |
326 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); | |
327 | ||
328 | if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) | |
329 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); | |
330 | ||
3354f500 | 331 | if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) |
9581bb84 LP |
332 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); |
333 | ||
334 | r = parse_as_address(link, &p); | |
335 | if (r != 0) | |
336 | return r; | |
337 | ||
338 | r = dns_question_new_address(&question_utf8, p.family, p.name, false); | |
339 | if (r < 0) | |
340 | return r; | |
341 | ||
342 | r = dns_question_new_address(&question_idna, p.family, p.name, true); | |
343 | if (r < 0 && r != -EALREADY) | |
344 | return r; | |
345 | ||
775ae354 | 346 | r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags); |
9581bb84 LP |
347 | if (r < 0) |
348 | return r; | |
349 | ||
350 | q->varlink_request = varlink_ref(link); | |
351 | varlink_set_userdata(link, q); | |
352 | q->request_family = p.family; | |
cb456374 SK |
353 | q->request_name = strdup(p.name); |
354 | if (!q->request_name) | |
355 | return log_oom(); | |
9581bb84 LP |
356 | q->complete = vl_method_resolve_hostname_complete; |
357 | ||
358 | r = dns_query_go(q); | |
359 | if (r < 0) | |
c704288c | 360 | return r; |
9581bb84 | 361 | |
c704288c | 362 | TAKE_PTR(q); |
9581bb84 | 363 | return 1; |
9581bb84 LP |
364 | } |
365 | ||
366 | static int json_dispatch_address(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { | |
99534007 | 367 | LookupParameters *p = ASSERT_PTR(userdata); |
9581bb84 LP |
368 | union in_addr_union buf = {}; |
369 | JsonVariant *i; | |
370 | size_t n, k = 0; | |
371 | ||
372 | assert(variant); | |
9581bb84 LP |
373 | |
374 | if (!json_variant_is_array(variant)) | |
375 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); | |
376 | ||
377 | n = json_variant_elements(variant); | |
378 | if (!IN_SET(n, 4, 16)) | |
379 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); | |
380 | ||
381 | JSON_VARIANT_ARRAY_FOREACH(i, variant) { | |
718ca772 | 382 | int64_t b; |
9581bb84 LP |
383 | |
384 | if (!json_variant_is_integer(i)) | |
385 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name)); | |
386 | ||
387 | b = json_variant_integer(i); | |
388 | if (b < 0 || b > 0xff) | |
28e5e1e9 DT |
389 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), |
390 | "Element %zu of JSON field '%s' is out of range 0%s255.", | |
391 | k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); | |
9581bb84 LP |
392 | |
393 | buf.bytes[k++] = (uint8_t) b; | |
394 | } | |
395 | ||
396 | p->address = buf; | |
397 | p->address_size = k; | |
398 | ||
399 | return 0; | |
400 | } | |
401 | ||
c704288c | 402 | static void vl_method_resolve_address_complete(DnsQuery *query) { |
9581bb84 | 403 | _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; |
c704288c | 404 | _cleanup_(dns_query_freep) DnsQuery *q = query; |
9581bb84 LP |
405 | DnsQuestion *question; |
406 | DnsResourceRecord *rr; | |
407 | int ifindex, r; | |
408 | ||
409 | assert(q); | |
410 | ||
411 | if (q->state != DNS_TRANSACTION_SUCCESS) { | |
412 | r = reply_query_state(q); | |
413 | goto finish; | |
414 | } | |
415 | ||
1db8e6d1 | 416 | r = dns_query_process_cname_many(q); |
9581bb84 LP |
417 | if (r == -ELOOP) { |
418 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); | |
419 | goto finish; | |
420 | } | |
421 | if (r < 0) | |
422 | goto finish; | |
c704288c YW |
423 | if (r == DNS_QUERY_CNAME) { |
424 | /* This was a cname, and the query was restarted. */ | |
425 | TAKE_PTR(q); | |
9581bb84 | 426 | return; |
c704288c | 427 | } |
9581bb84 LP |
428 | |
429 | question = dns_query_question_for_protocol(q, q->answer_protocol); | |
430 | ||
431 | DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { | |
432 | _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; | |
433 | _cleanup_free_ char *normalized = NULL; | |
434 | ||
435 | r = dns_question_matches_rr(question, rr, NULL); | |
436 | if (r < 0) | |
437 | goto finish; | |
438 | if (r == 0) | |
439 | continue; | |
440 | ||
441 | r = dns_name_normalize(rr->ptr.name, 0, &normalized); | |
442 | if (r < 0) | |
443 | goto finish; | |
444 | ||
445 | r = json_build(&entry, | |
446 | JSON_BUILD_OBJECT( | |
f8f5b8d8 | 447 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), |
9581bb84 LP |
448 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)))); |
449 | if (r < 0) | |
450 | goto finish; | |
451 | ||
452 | r = json_variant_append_array(&array, entry); | |
453 | if (r < 0) | |
454 | goto finish; | |
455 | } | |
456 | ||
457 | if (json_variant_is_blank_object(array)) { | |
458 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
459 | goto finish; | |
460 | } | |
461 | ||
462 | r = varlink_replyb(q->varlink_request, | |
463 | JSON_BUILD_OBJECT( | |
464 | JSON_BUILD_PAIR("names", JSON_BUILD_VARIANT(array)), | |
43fc4baa | 465 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); |
9581bb84 LP |
466 | finish: |
467 | if (r < 0) { | |
468 | log_error_errno(r, "Failed to send address reply: %m"); | |
469 | r = varlink_error_errno(q->varlink_request, r); | |
470 | } | |
9581bb84 LP |
471 | } |
472 | ||
473 | static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
474 | static const JsonDispatch dispatch_table[] = { | |
475 | { "ifindex", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, | |
476 | { "family", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, family), JSON_MANDATORY }, | |
477 | { "address", JSON_VARIANT_ARRAY, json_dispatch_address, 0, JSON_MANDATORY }, | |
478 | { "flags", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, | |
479 | {} | |
480 | }; | |
481 | ||
482 | _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; | |
483 | _cleanup_(lookup_parameters_destroy) LookupParameters p = { | |
484 | .family = AF_UNSPEC, | |
485 | }; | |
c704288c | 486 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
9807fdc1 | 487 | Manager *m; |
9581bb84 LP |
488 | int r; |
489 | ||
490 | assert(link); | |
9807fdc1 LP |
491 | |
492 | m = varlink_server_get_userdata(varlink_get_server(link)); | |
9581bb84 LP |
493 | assert(m); |
494 | ||
495 | if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) | |
496 | return -EINVAL; | |
497 | ||
498 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
499 | if (r < 0) | |
500 | return r; | |
501 | ||
502 | if (p.ifindex < 0) | |
503 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); | |
504 | ||
0234f0c0 | 505 | if (!IN_SET(p.family, AF_INET, AF_INET6)) |
9581bb84 LP |
506 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); |
507 | ||
508 | if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size) | |
509 | return varlink_error(link, "io.systemd.UserDatabase.BadAddressSize", NULL); | |
510 | ||
3354f500 | 511 | if (!validate_and_mangle_flags(NULL, &p.flags, 0)) |
9581bb84 LP |
512 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); |
513 | ||
514 | r = dns_question_new_reverse(&question, p.family, &p.address); | |
515 | if (r < 0) | |
516 | return r; | |
517 | ||
775ae354 | 518 | r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); |
9581bb84 LP |
519 | if (r < 0) |
520 | return r; | |
521 | ||
522 | q->varlink_request = varlink_ref(link); | |
523 | varlink_set_userdata(link, q); | |
524 | ||
525 | q->request_family = p.family; | |
526 | q->request_address = p.address; | |
527 | q->complete = vl_method_resolve_address_complete; | |
528 | ||
529 | r = dns_query_go(q); | |
530 | if (r < 0) | |
c704288c | 531 | return r; |
9581bb84 | 532 | |
c704288c | 533 | TAKE_PTR(q); |
9581bb84 | 534 | return 1; |
9581bb84 LP |
535 | } |
536 | ||
cb456374 SK |
537 | static int vl_method_subscribe_dns_resolves(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { |
538 | Manager *m; | |
539 | int r; | |
540 | ||
541 | assert(link); | |
542 | ||
543 | m = varlink_server_get_userdata(varlink_get_server(link)); | |
544 | assert(m); | |
545 | ||
546 | if (json_variant_elements(parameters) > 0) | |
547 | return varlink_error_invalid_parameter(link, parameters); | |
548 | ||
549 | r = set_ensure_put(&m->varlink_subscription, NULL, link); | |
550 | if (r < 0) | |
551 | return log_error_errno(r, "Failed to add subscription to set: %m"); | |
552 | varlink_ref(link); | |
553 | ||
554 | log_debug("%u clients now attached for varlink notifications", set_size(m->varlink_subscription)); | |
555 | ||
556 | /* if the client didn't set the more flag, return an empty response and close the connection */ | |
557 | if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) | |
558 | return varlink_reply(link, NULL); | |
559 | ||
560 | return 1; | |
561 | } | |
562 | ||
9581bb84 LP |
563 | int manager_varlink_init(Manager *m) { |
564 | _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; | |
565 | int r; | |
566 | ||
567 | assert(m); | |
568 | ||
569 | if (m->varlink_server) | |
570 | return 0; | |
571 | ||
572 | r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID); | |
573 | if (r < 0) | |
574 | return log_error_errno(r, "Failed to allocate varlink server object: %m"); | |
575 | ||
576 | varlink_server_set_userdata(s, m); | |
577 | ||
578 | r = varlink_server_bind_method_many( | |
579 | s, | |
580 | "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, | |
581 | "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address); | |
582 | if (r < 0) | |
583 | return log_error_errno(r, "Failed to register varlink methods: %m"); | |
584 | ||
585 | r = varlink_server_bind_disconnect(s, vl_on_disconnect); | |
586 | if (r < 0) | |
587 | return log_error_errno(r, "Failed to register varlink disconnect handler: %m"); | |
588 | ||
589 | r = varlink_server_listen_address(s, "/run/systemd/resolve/io.systemd.Resolve", 0666); | |
590 | if (r < 0) | |
591 | return log_error_errno(r, "Failed to bind to varlink socket: %m"); | |
592 | ||
593 | r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); | |
594 | if (r < 0) | |
595 | return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); | |
596 | ||
597 | m->varlink_server = TAKE_PTR(s); | |
cb456374 SK |
598 | |
599 | if (m->enable_varlink_notifications) { | |
600 | if (m->varlink_notification_server) | |
601 | return 0; | |
602 | ||
603 | r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID); | |
604 | if (r < 0) | |
605 | return log_error_errno(r, "Failed to allocate varlink server object: %m"); | |
606 | ||
607 | varlink_server_set_userdata(s, m); | |
608 | ||
609 | r = varlink_server_bind_method_many( | |
610 | s, | |
611 | "io.systemd.Resolve.Monitor.SubscribeDnsResolves", | |
612 | vl_method_subscribe_dns_resolves); | |
613 | if (r < 0) | |
614 | return log_error_errno(r, "Failed to register varlink methods: %m"); | |
615 | ||
616 | r = varlink_server_bind_disconnect(s, vl_on_notification_disconnect); | |
617 | if (r < 0) | |
618 | return log_error_errno(r, "Failed to register varlink disconnect handler: %m"); | |
619 | ||
620 | r = varlink_server_listen_address(s, "/run/systemd/resolve/io.systemd.Resolve.Monitor", 0660); | |
621 | if (r < 0) | |
622 | return log_error_errno(r, "Failed to bind to varlink socket: %m"); | |
623 | ||
624 | r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); | |
625 | if (r < 0) | |
626 | return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); | |
627 | ||
628 | m->varlink_notification_server = TAKE_PTR(s); | |
629 | } | |
630 | ||
9581bb84 LP |
631 | return 0; |
632 | } | |
633 | ||
634 | void manager_varlink_done(Manager *m) { | |
635 | assert(m); | |
636 | ||
637 | m->varlink_server = varlink_server_unref(m->varlink_server); | |
cb456374 | 638 | m->varlink_notification_server = varlink_server_unref(m->varlink_notification_server); |
9581bb84 | 639 | } |