]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-bus.c
dns-domain: simplify dns_name_is_root() and dns_name_is_single_label()
[thirdparty/systemd.git] / src / resolve / resolved-bus.c
CommitLineData
74b2466e
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
b5efdb8a 22#include "alloc-util.h"
96aad8d1 23#include "bus-common-errors.h"
74b2466e 24#include "bus-util.h"
4ad7f276 25#include "dns-domain.h"
39d8db04 26#include "resolved-bus.h"
51323288 27#include "resolved-def.h"
74b2466e 28
ad867662
LP
29static int reply_query_state(DnsQuery *q) {
30 _cleanup_free_ char *ip = NULL;
31 const char *name;
74b2466e
LP
32 int r;
33
45ec7efb 34 if (q->request_address_valid) {
ad867662
LP
35 r = in_addr_to_string(q->request_family, &q->request_address, &ip);
36 if (r < 0)
37 return r;
74b2466e 38
ad867662 39 name = ip;
45ec7efb 40 } else
703e4f5e 41 name = dns_question_first_name(q->question);
ad867662
LP
42
43 switch (q->state) {
74b2466e 44
ec2c5e43 45 case DNS_TRANSACTION_NO_SERVERS:
309e9d86 46 return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found");
74b2466e 47
ec2c5e43 48 case DNS_TRANSACTION_TIMEOUT:
ad867662 49 return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out");
74b2466e 50
ec2c5e43 51 case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED:
ad867662
LP
52 return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed");
53
818f766b
LP
54 case DNS_TRANSACTION_INVALID_REPLY:
55 return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply");
56
ec2c5e43 57 case DNS_TRANSACTION_RESOURCES:
ad867662
LP
58 return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources");
59
818f766b
LP
60 case DNS_TRANSACTION_ABORTED:
61 return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted");
74b2466e 62
ec2c5e43 63 case DNS_TRANSACTION_FAILURE: {
74b2466e
LP
64 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
65
faa133f3 66 if (q->answer_rcode == DNS_RCODE_NXDOMAIN)
ad867662 67 sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name);
74b2466e
LP
68 else {
69 const char *rc, *n;
ad867662 70 char p[3]; /* the rcode is 4 bits long */
74b2466e 71
faa133f3 72 rc = dns_rcode_to_string(q->answer_rcode);
74b2466e 73 if (!rc) {
faa133f3 74 sprintf(p, "%i", q->answer_rcode);
74b2466e
LP
75 rc = p;
76 }
77
63c372cb 78 n = strjoina(_BUS_ERROR_DNS, rc);
ad867662 79 sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc);
74b2466e
LP
80 }
81
ad867662 82 return sd_bus_reply_method_error(q->request, &error);
74b2466e
LP
83 }
84
ec2c5e43
LP
85 case DNS_TRANSACTION_NULL:
86 case DNS_TRANSACTION_PENDING:
87 case DNS_TRANSACTION_SUCCESS:
8ba9fd9c 88 default:
ad867662
LP
89 assert_not_reached("Impossible state");
90 }
91}
74b2466e 92
78c6a153 93static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) {
8ba9fd9c
LP
94 int r;
95
96 assert(reply);
97 assert(rr);
98
78c6a153
LP
99 r = sd_bus_message_open_container(reply, 'r', "iiay");
100 if (r < 0)
101 return r;
102
103 r = sd_bus_message_append(reply, "i", ifindex);
8ba9fd9c
LP
104 if (r < 0)
105 return r;
106
faa133f3 107 if (rr->key->type == DNS_TYPE_A) {
0dd25fb9 108 r = sd_bus_message_append(reply, "i", AF_INET);
8ba9fd9c
LP
109 if (r < 0)
110 return r;
111
112 r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr));
faa133f3
LP
113
114 } else if (rr->key->type == DNS_TYPE_AAAA) {
0dd25fb9 115 r = sd_bus_message_append(reply, "i", AF_INET6);
8ba9fd9c
LP
116 if (r < 0)
117 return r;
118
119 r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr));
faa133f3
LP
120 } else
121 return -EAFNOSUPPORT;
122
8ba9fd9c
LP
123 if (r < 0)
124 return r;
125
8ba9fd9c
LP
126 r = sd_bus_message_close_container(reply);
127 if (r < 0)
128 return r;
129
130 return 0;
131}
132
ad867662 133static void bus_method_resolve_hostname_complete(DnsQuery *q) {
45ec7efb 134 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
ad867662 135 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
45ec7efb 136 unsigned added = 0;
51323288 137 int r;
74b2466e 138
ad867662 139 assert(q);
74b2466e 140
ec2c5e43 141 if (q->state != DNS_TRANSACTION_SUCCESS) {
ad867662
LP
142 r = reply_query_state(q);
143 goto finish;
144 }
74b2466e 145
45ec7efb
LP
146 r = dns_query_process_cname(q);
147 if (r == -ELOOP) {
703e4f5e 148 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
45ec7efb
LP
149 goto finish;
150 }
151 if (r < 0)
152 goto finish;
153 if (r > 0) /* This was a cname, and the query was restarted. */
154 return;
155
ad867662
LP
156 r = sd_bus_message_new_method_return(q->request, &reply);
157 if (r < 0)
158 goto finish;
74b2466e 159
78c6a153 160 r = sd_bus_message_open_container(reply, 'a', "(iiay)");
51323288
LP
161 if (r < 0)
162 goto finish;
8ba9fd9c 163
3339cb71 164 if (q->answer) {
45ec7efb
LP
165 DnsResourceRecord *rr;
166 int ifindex;
3339cb71 167
45ec7efb 168 DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
801ad6a6 169 r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
8ba9fd9c 170 if (r < 0)
2d4c5cbc 171 goto finish;
45ec7efb 172 if (r == 0)
3339cb71 173 continue;
74b2466e 174
45ec7efb 175 r = append_address(reply, rr, ifindex);
3339cb71
LP
176 if (r < 0)
177 goto finish;
74b2466e 178
3339cb71 179 if (!canonical)
45ec7efb 180 canonical = dns_resource_record_ref(rr);
309e9d86 181
3339cb71
LP
182 added ++;
183 }
8ba9fd9c 184 }
74b2466e 185
45ec7efb 186 if (added <= 0) {
703e4f5e 187 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
45ec7efb 188 goto finish;
74b2466e
LP
189 }
190
ad867662
LP
191 r = sd_bus_message_close_container(reply);
192 if (r < 0)
193 goto finish;
194
45ec7efb 195 /* Return the precise spelling and uppercasing and CNAME target reported by the server */
309e9d86 196 assert(canonical);
45ec7efb
LP
197 r = sd_bus_message_append(
198 reply, "st",
199 DNS_RESOURCE_KEY_NAME(canonical->key),
200 SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
309e9d86
LP
201 if (r < 0)
202 goto finish;
203
ad867662 204 r = sd_bus_send(q->manager->bus, reply, NULL);
ad867662 205
74b2466e 206finish:
2d4c5cbc 207 if (r < 0) {
da927ba9 208 log_error_errno(r, "Failed to send hostname reply: %m");
45ec7efb 209 sd_bus_reply_method_errno(q->request, r, NULL);
2d4c5cbc 210 }
74b2466e
LP
211
212 dns_query_free(q);
213}
214
45ec7efb 215static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) {
51323288
LP
216 assert(flags);
217
218 if (ifindex < 0)
219 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index");
220
45ec7efb 221 if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok))
51323288
LP
222 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
223
45ec7efb
LP
224 if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
225 *flags |= SD_RESOLVED_PROTOCOLS_ALL;
51323288
LP
226
227 return 0;
228}
229
19070062 230static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) {
faa133f3 231 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
74b2466e
LP
232 Manager *m = userdata;
233 const char *hostname;
51323288
LP
234 int family, ifindex;
235 uint64_t flags;
74b2466e 236 DnsQuery *q;
74b2466e
LP
237 int r;
238
74b2466e
LP
239 assert(message);
240 assert(m);
241
45ec7efb
LP
242 assert_cc(sizeof(int) == sizeof(int32_t));
243
51323288 244 r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags);
74b2466e
LP
245 if (r < 0)
246 return r;
247
248 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
0dd25fb9 249 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
74b2466e 250
45ec7efb 251 r = dns_name_is_valid(hostname);
7b9f7afc 252 if (r < 0)
45ec7efb
LP
253 return r;
254 if (r == 0)
74b2466e
LP
255 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname);
256
801ad6a6 257 r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error);
51323288
LP
258 if (r < 0)
259 return r;
260
45ec7efb
LP
261 r = dns_question_new_address(&question, family, hostname);
262 if (r < 0)
263 return r;
74b2466e 264
51323288 265 r = dns_query_new(m, &q, question, ifindex, flags);
74b2466e
LP
266 if (r < 0)
267 return r;
268
269 q->request = sd_bus_message_ref(message);
270 q->request_family = family;
74b2466e
LP
271 q->complete = bus_method_resolve_hostname_complete;
272
966c66e3 273 r = dns_query_bus_track(q, message);
82bd6ddd 274 if (r < 0)
45ec7efb 275 goto fail;
82bd6ddd 276
322345fd 277 r = dns_query_go(q);
45ec7efb
LP
278 if (r < 0)
279 goto fail;
74b2466e
LP
280
281 return 1;
45ec7efb
LP
282
283fail:
284 dns_query_free(q);
285 return r;
74b2466e
LP
286}
287
288static void bus_method_resolve_address_complete(DnsQuery *q) {
ad867662 289 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
45ec7efb
LP
290 DnsResourceRecord *rr;
291 unsigned added = 0;
292 int ifindex, r;
74b2466e
LP
293
294 assert(q);
295
ec2c5e43 296 if (q->state != DNS_TRANSACTION_SUCCESS) {
ad867662
LP
297 r = reply_query_state(q);
298 goto finish;
299 }
74b2466e 300
45ec7efb
LP
301 /* We don't process CNAME for PTR lookups. */
302
ad867662
LP
303 r = sd_bus_message_new_method_return(q->request, &reply);
304 if (r < 0)
305 goto finish;
74b2466e 306
78c6a153 307 r = sd_bus_message_open_container(reply, 'a', "(is)");
ad867662
LP
308 if (r < 0)
309 goto finish;
74b2466e 310
3339cb71 311 if (q->answer) {
45ec7efb 312 DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
801ad6a6 313 r = dns_question_matches_rr(q->question, rr, NULL);
3339cb71 314 if (r < 0)
2d4c5cbc 315 goto finish;
3339cb71
LP
316 if (r == 0)
317 continue;
74b2466e 318
45ec7efb 319 r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
3339cb71
LP
320 if (r < 0)
321 goto finish;
74b2466e 322
3339cb71
LP
323 added ++;
324 }
ad867662 325 }
74b2466e 326
45ec7efb 327 if (added <= 0) {
ad867662 328 _cleanup_free_ char *ip = NULL;
74b2466e 329
ad867662 330 in_addr_to_string(q->request_family, &q->request_address, &ip);
45ec7efb 331 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip));
ad867662 332 goto finish;
74b2466e
LP
333 }
334
ad867662
LP
335 r = sd_bus_message_close_container(reply);
336 if (r < 0)
337 goto finish;
74b2466e 338
51323288
LP
339 r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
340 if (r < 0)
341 goto finish;
342
ad867662 343 r = sd_bus_send(q->manager->bus, reply, NULL);
74b2466e
LP
344
345finish:
2d4c5cbc 346 if (r < 0) {
da927ba9 347 log_error_errno(r, "Failed to send address reply: %m");
45ec7efb 348 sd_bus_reply_method_errno(q->request, r, NULL);
2d4c5cbc 349 }
74b2466e
LP
350
351 dns_query_free(q);
352}
353
19070062 354static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) {
faa133f3 355 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
74b2466e 356 Manager *m = userdata;
faa133f3 357 int family, ifindex;
51323288 358 uint64_t flags;
74b2466e 359 const void *d;
74b2466e
LP
360 DnsQuery *q;
361 size_t sz;
362 int r;
363
74b2466e
LP
364 assert(message);
365 assert(m);
366
45ec7efb
LP
367 assert_cc(sizeof(int) == sizeof(int32_t));
368
51323288 369 r = sd_bus_message_read(message, "ii", &ifindex, &family);
74b2466e
LP
370 if (r < 0)
371 return r;
372
373 if (!IN_SET(family, AF_INET, AF_INET6))
0dd25fb9 374 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
74b2466e
LP
375
376 r = sd_bus_message_read_array(message, 'y', &d, &sz);
377 if (r < 0)
378 return r;
379
0dd25fb9 380 if (sz != FAMILY_ADDRESS_SIZE(family))
74b2466e
LP
381 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size");
382
51323288
LP
383 r = sd_bus_message_read(message, "t", &flags);
384 if (r < 0)
385 return r;
386
45ec7efb 387 r = check_ifindex_flags(ifindex, &flags, 0, error);
faa133f3
LP
388 if (r < 0)
389 return r;
390
45ec7efb 391 r = dns_question_new_reverse(&question, family, d);
74b2466e
LP
392 if (r < 0)
393 return r;
394
801ad6a6 395 r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
74b2466e
LP
396 if (r < 0)
397 return r;
398
399 q->request = sd_bus_message_ref(message);
400 q->request_family = family;
401 memcpy(&q->request_address, d, sz);
402 q->complete = bus_method_resolve_address_complete;
403
966c66e3 404 r = dns_query_bus_track(q, message);
82bd6ddd 405 if (r < 0)
45ec7efb 406 goto fail;
82bd6ddd 407
322345fd 408 r = dns_query_go(q);
45ec7efb
LP
409 if (r < 0)
410 goto fail;
74b2466e
LP
411
412 return 1;
45ec7efb
LP
413
414fail:
415 dns_query_free(q);
416 return r;
417}
418
419static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) {
420 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
421 size_t start;
422 int r;
423
424 assert(m);
425 assert(rr);
426
427 r = sd_bus_message_open_container(m, 'r', "iqqay");
428 if (r < 0)
429 return r;
430
431 r = sd_bus_message_append(m, "iqq",
432 ifindex,
433 rr->key->class,
434 rr->key->type);
435 if (r < 0)
436 return r;
437
438 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
439 if (r < 0)
440 return r;
441
442 p->refuse_compression = true;
443
444 r = dns_packet_append_rr(p, rr, &start);
445 if (r < 0)
446 return r;
447
448 r = sd_bus_message_append_array(m, 'y', DNS_PACKET_DATA(p) + start, p->size - start);
449 if (r < 0)
450 return r;
451
452 return sd_bus_message_close_container(m);
74b2466e
LP
453}
454
2d4c5cbc
LP
455static void bus_method_resolve_record_complete(DnsQuery *q) {
456 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
45ec7efb 457 unsigned added = 0;
2d4c5cbc
LP
458 int r;
459
460 assert(q);
461
ec2c5e43 462 if (q->state != DNS_TRANSACTION_SUCCESS) {
2d4c5cbc
LP
463 r = reply_query_state(q);
464 goto finish;
465 }
466
45ec7efb
LP
467 r = dns_query_process_cname(q);
468 if (r == -ELOOP) {
703e4f5e 469 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
45ec7efb
LP
470 goto finish;
471 }
472 if (r < 0)
473 goto finish;
474 if (r > 0) /* Following a CNAME */
475 return;
476
2d4c5cbc
LP
477 r = sd_bus_message_new_method_return(q->request, &reply);
478 if (r < 0)
479 goto finish;
480
78c6a153 481 r = sd_bus_message_open_container(reply, 'a', "(iqqay)");
2d4c5cbc
LP
482 if (r < 0)
483 goto finish;
484
485 if (q->answer) {
45ec7efb
LP
486 DnsResourceRecord *rr;
487 int ifindex;
2d4c5cbc 488
45ec7efb 489 DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
801ad6a6 490 r = dns_question_matches_rr(q->question, rr, NULL);
2d4c5cbc
LP
491 if (r < 0)
492 goto finish;
493 if (r == 0)
494 continue;
495
45ec7efb 496 r = bus_message_append_rr(reply, rr, ifindex);
2d4c5cbc
LP
497 if (r < 0)
498 goto finish;
499
500 added ++;
501 }
502 }
503
504 if (added <= 0) {
703e4f5e 505 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_first_name(q->question));
2d4c5cbc
LP
506 goto finish;
507 }
508
509 r = sd_bus_message_close_container(reply);
510 if (r < 0)
511 goto finish;
512
51323288
LP
513 r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
514 if (r < 0)
515 goto finish;
516
2d4c5cbc
LP
517 r = sd_bus_send(q->manager->bus, reply, NULL);
518
519finish:
520 if (r < 0) {
da927ba9 521 log_error_errno(r, "Failed to send record reply: %m");
45ec7efb 522 sd_bus_reply_method_errno(q->request, r, NULL);
2d4c5cbc
LP
523 }
524
525 dns_query_free(q);
526}
527
19070062 528static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2d4c5cbc
LP
529 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
530 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
2d4c5cbc 531 Manager *m = userdata;
2d4c5cbc
LP
532 uint16_t class, type;
533 const char *name;
51323288
LP
534 int r, ifindex;
535 uint64_t flags;
536 DnsQuery *q;
2d4c5cbc 537
2d4c5cbc
LP
538 assert(message);
539 assert(m);
540
45ec7efb
LP
541 assert_cc(sizeof(int) == sizeof(int32_t));
542
51323288 543 r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags);
2d4c5cbc
LP
544 if (r < 0)
545 return r;
546
45ec7efb 547 r = dns_name_is_valid(name);
7b9f7afc 548 if (r < 0)
45ec7efb
LP
549 return r;
550 if (r == 0)
7b9f7afc
LP
551 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name);
552
45ec7efb 553 r = check_ifindex_flags(ifindex, &flags, 0, error);
51323288
LP
554 if (r < 0)
555 return r;
556
2d4c5cbc
LP
557 question = dns_question_new(1);
558 if (!question)
559 return -ENOMEM;
560
561 key = dns_resource_key_new(class, type, name);
562 if (!key)
563 return -ENOMEM;
564
565 r = dns_question_add(question, key);
566 if (r < 0)
567 return r;
568
801ad6a6 569 r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
2d4c5cbc
LP
570 if (r < 0)
571 return r;
572
573 q->request = sd_bus_message_ref(message);
2d4c5cbc
LP
574 q->complete = bus_method_resolve_record_complete;
575
966c66e3 576 r = dns_query_bus_track(q, message);
82bd6ddd 577 if (r < 0)
45ec7efb 578 goto fail;
82bd6ddd 579
2d4c5cbc 580 r = dns_query_go(q);
45ec7efb
LP
581 if (r < 0)
582 goto fail;
583
584 return 1;
585
586fail:
587 dns_query_free(q);
588 return r;
589}
590
591static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) {
592 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
593 DnsQuery *aux;
594 int r;
595
596 assert(q);
597 assert(reply);
598 assert(rr);
599 assert(rr->key);
600
601 if (rr->key->type != DNS_TYPE_SRV)
602 return 0;
603
604 if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
605 /* First, let's see if we could find an appropriate A or AAAA
606 * record for the SRV record */
607 LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
608 DnsResourceRecord *zz;
609
610 if (aux->state != DNS_TRANSACTION_SUCCESS)
611 continue;
612 if (aux->auxiliary_result != 0)
613 continue;
614
703e4f5e 615 r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
45ec7efb
LP
616 if (r < 0)
617 return r;
618 if (r == 0)
619 continue;
620
621 DNS_ANSWER_FOREACH(zz, aux->answer) {
622
801ad6a6 623 r = dns_question_matches_rr(aux->question, zz, NULL);
45ec7efb
LP
624 if (r < 0)
625 return r;
626 if (r == 0)
627 continue;
628
629 canonical = dns_resource_record_ref(zz);
630 break;
631 }
632
633 if (canonical)
634 break;
635 }
636
637 /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */
638 if (!canonical)
639 return 0;
640 }
641
642 r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s");
643 if (r < 0)
644 return r;
645
646 r = sd_bus_message_append(
647 reply,
648 "qqqs",
649 rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name);
650 if (r < 0)
651 return r;
652
653 r = sd_bus_message_open_container(reply, 'a', "(iiay)");
654 if (r < 0)
655 return r;
656
657 if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
658 LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
659 DnsResourceRecord *zz;
660 int ifindex;
661
662 if (aux->state != DNS_TRANSACTION_SUCCESS)
663 continue;
664 if (aux->auxiliary_result != 0)
665 continue;
666
703e4f5e 667 r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
45ec7efb
LP
668 if (r < 0)
669 return r;
670 if (r == 0)
671 continue;
672
673 DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) {
674
801ad6a6 675 r = dns_question_matches_rr(aux->question, zz, NULL);
45ec7efb
LP
676 if (r < 0)
677 return r;
678 if (r == 0)
679 continue;
680
681 r = append_address(reply, zz, ifindex);
682 if (r < 0)
683 return r;
684 }
685 }
686 }
687
688 r = sd_bus_message_close_container(reply);
689 if (r < 0)
690 return r;
691
692 /* Note that above we appended the hostname as encoded in the
693 * SRV, and here the canonical hostname this maps to. */
694 r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name);
695 if (r < 0)
696 return r;
697
698 r = sd_bus_message_close_container(reply);
699 if (r < 0)
700 return r;
701
702 return 1;
703}
704
705static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) {
706 DnsTxtItem *i;
707 int r;
708
709 assert(reply);
710 assert(rr);
711 assert(rr->key);
712
713 if (rr->key->type != DNS_TYPE_TXT)
714 return 0;
715
716 LIST_FOREACH(items, i, rr->txt.items) {
717
718 if (i->length <= 0)
719 continue;
720
721 r = sd_bus_message_append_array(reply, 'y', i->data, i->length);
722 if (r < 0)
723 return r;
724 }
725
726 return 1;
727}
728
729static void resolve_service_all_complete(DnsQuery *q) {
730 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
731 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
732 _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
733 DnsQuery *aux;
734 unsigned added = false;
735 int r;
736
737 assert(q);
738
739 if (q->block_all_complete > 0)
740 return;
741
742 if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
743 DnsQuery *bad = NULL;
744 bool have_success = false;
745
746 LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
747
748 switch (aux->state) {
749
750 case DNS_TRANSACTION_PENDING:
751 /* If an auxiliary query is still pending, let's wait */
752 return;
753
754 case DNS_TRANSACTION_SUCCESS:
755 if (aux->auxiliary_result == 0)
756 have_success = true;
757 else
758 bad = aux;
759 break;
760
761 default:
762 bad = aux;
763 break;
764 }
765 }
766
767 if (!have_success) {
768 /* We can only return one error, hence pick the last error we encountered */
769
770 assert(bad);
771
772 if (bad->state == DNS_TRANSACTION_SUCCESS) {
773 assert(bad->auxiliary_result != 0);
774
775 if (bad->auxiliary_result == -ELOOP) {
703e4f5e 776 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(bad->question));
45ec7efb
LP
777 goto finish;
778 }
779
780 r = bad->auxiliary_result;
781 goto finish;
782 }
783
784 r = reply_query_state(bad);
785 goto finish;
786 }
787 }
788
789 r = sd_bus_message_new_method_return(q->request, &reply);
790 if (r < 0)
791 goto finish;
792
793 r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)");
794 if (r < 0)
795 goto finish;
796
797 if (q->answer) {
798 DnsResourceRecord *rr;
799
800 DNS_ANSWER_FOREACH(rr, q->answer) {
801ad6a6 801 r = dns_question_matches_rr(q->question, rr, NULL);
45ec7efb
LP
802 if (r < 0)
803 goto finish;
804 if (r == 0)
805 continue;
806
807 r = append_srv(q, reply, rr);
808 if (r < 0)
809 goto finish;
810 if (r == 0) /* not an SRV record */
811 continue;
812
813 if (!canonical)
814 canonical = dns_resource_record_ref(rr);
815
816 added++;
817 }
818 }
819
820 if (added <= 0) {
703e4f5e 821 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
45ec7efb
LP
822 goto finish;
823 }
824
825 r = sd_bus_message_close_container(reply);
826 if (r < 0)
827 goto finish;
828
829 r = sd_bus_message_open_container(reply, 'a', "ay");
830 if (r < 0)
831 goto finish;
832
833 if (q->answer) {
834 DnsResourceRecord *rr;
835
836 DNS_ANSWER_FOREACH(rr, q->answer) {
801ad6a6 837 r = dns_question_matches_rr(q->question, rr, NULL);
45ec7efb
LP
838 if (r < 0)
839 goto finish;
840 if (r == 0)
841 continue;
842
843 r = append_txt(reply, rr);
844 if (r < 0)
845 goto finish;
846 }
847 }
848
849 r = sd_bus_message_close_container(reply);
850 if (r < 0)
851 goto finish;
852
853 assert(canonical);
854 r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain);
855 if (r < 0)
856 goto finish;
857
858 r = sd_bus_message_append(
859 reply,
860 "ssst",
861 name, type, domain,
862 SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
863 if (r < 0)
864 goto finish;
865
866 r = sd_bus_send(q->manager->bus, reply, NULL);
867
868finish:
869 if (r < 0) {
870 log_error_errno(r, "Failed to send service reply: %m");
871 sd_bus_reply_method_errno(q->request, r, NULL);
872 }
873
874 dns_query_free(q);
875}
876
877static void resolve_service_hostname_complete(DnsQuery *q) {
878 int r;
879
880 assert(q);
881 assert(q->auxiliary_for);
882
883 if (q->state != DNS_TRANSACTION_SUCCESS) {
884 resolve_service_all_complete(q->auxiliary_for);
885 return;
886 }
887
888 r = dns_query_process_cname(q);
889 if (r > 0) /* This was a cname, and the query was restarted. */
890 return;
891
892 /* This auxiliary lookup is finished or failed, let's see if all are finished now. */
893 q->auxiliary_result = r;
894 resolve_service_all_complete(q->auxiliary_for);
895}
896
897static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) {
898 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
899 DnsQuery *aux;
900 int r;
901
902 assert(q);
903 assert(rr);
904 assert(rr->key);
905 assert(rr->key->type == DNS_TYPE_SRV);
906
907 /* OK, we found an SRV record for the service. Let's resolve
908 * the hostname included in it */
909
910 r = dns_question_new_address(&question, q->request_family, rr->srv.name);
911 if (r < 0)
912 return r;
913
801ad6a6 914 r = dns_query_new(q->manager, &aux, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
45ec7efb
LP
915 if (r < 0)
916 return r;
917
918 aux->request_family = q->request_family;
919 aux->complete = resolve_service_hostname_complete;
920
921 r = dns_query_make_auxiliary(aux, q);
922 if (r == -EAGAIN) {
923 /* Too many auxiliary lookups? If so, don't complain,
924 * let's just not add this one, we already have more
925 * than enough */
926
927 dns_query_free(aux);
928 return 0;
929 }
930 if (r < 0)
931 goto fail;
932
933 /* Note that auxiliary queries do not track the original bus
934 * client, only the primary request does that. */
935
936 r = dns_query_go(aux);
937 if (r < 0)
938 goto fail;
939
940 return 1;
941
942fail:
943 dns_query_free(aux);
944 return r;
945}
946
947static void bus_method_resolve_service_complete(DnsQuery *q) {
948 unsigned found = 0;
949 int r;
950
951 assert(q);
952
953 if (q->state != DNS_TRANSACTION_SUCCESS) {
954 r = reply_query_state(q);
955 goto finish;
956 }
957
958 r = dns_query_process_cname(q);
959 if (r == -ELOOP) {
703e4f5e 960 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
45ec7efb
LP
961 goto finish;
962 }
963 if (r < 0)
964 goto finish;
965 if (r > 0) /* This was a cname, and the query was restarted. */
966 return;
967
968 if (q->answer) {
969 DnsResourceRecord *rr;
970 int ifindex;
971
972 DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
801ad6a6 973 r = dns_question_matches_rr(q->question, rr, NULL);
45ec7efb
LP
974 if (r < 0)
975 goto finish;
976 if (r == 0)
977 continue;
978
979 if (rr->key->type != DNS_TYPE_SRV)
980 continue;
981
982 if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
983 q->block_all_complete ++;
984 r = resolve_service_hostname(q, rr, ifindex);
985 q->block_all_complete --;
986
987 if (r < 0)
988 goto finish;
989 }
990
991 found++;
992 }
993 }
994
995 if (found <= 0) {
703e4f5e 996 r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
45ec7efb
LP
997 goto finish;
998 }
999
1000 /* Maybe we are already finished? check now... */
1001 resolve_service_all_complete(q);
1002 return;
1003
1004finish:
2d4c5cbc 1005 if (r < 0) {
45ec7efb
LP
1006 log_error_errno(r, "Failed to send service reply: %m");
1007 sd_bus_reply_method_errno(q->request, r, NULL);
1008 }
1009
1010 dns_query_free(q);
1011}
1012
1013static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
45ec7efb
LP
1014 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
1015 const char *name, *type, *domain, *joined;
1016 _cleanup_free_ char *n = NULL;
1017 Manager *m = userdata;
1018 int family, ifindex;
1019 uint64_t flags;
1020 DnsQuery *q;
1021 int r;
1022
1023 assert(message);
1024 assert(m);
1025
1026 assert_cc(sizeof(int) == sizeof(int32_t));
1027
1028 r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags);
1029 if (r < 0)
2d4c5cbc 1030 return r;
45ec7efb
LP
1031
1032 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
1033 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
1034
1035 if (isempty(name))
1036 name = NULL;
1037 else {
1038 if (!dns_service_name_is_valid(name))
1039 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
2d4c5cbc
LP
1040 }
1041
45ec7efb
LP
1042 if (isempty(type))
1043 type = NULL;
1044 else {
1045 r = dns_srv_type_verify(type);
1046 if (r < 0)
1047 return r;
1048 if (r == 0)
1049 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type);
1050 }
1051
1052 r = dns_name_is_valid(domain);
1053 if (r < 0)
1054 return r;
1055 if (r == 0)
1056 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain);
1057
1058 if (name && !type)
1059 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type.");
1060
1061 r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error);
1062 if (r < 0)
1063 return r;
1064
1065 if (type) {
1066 /* If the type is specified, we generate the full domain name to look up ourselves */
1067 r = dns_service_join(name, type, domain, &n);
1068 if (r < 0)
1069 return r;
1070
1071 joined = n;
1072 } else
1073 /* If no type is specified, we assume the domain
1074 * contains the full domain name to lookup already */
1075 joined = domain;
1076
1077 r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT));
1078 if (r < 0)
1079 return r;
1080
801ad6a6 1081 r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
45ec7efb
LP
1082 if (r < 0)
1083 return r;
1084
1085 q->request = sd_bus_message_ref(message);
1086 q->request_family = family;
1087 q->complete = bus_method_resolve_service_complete;
1088
1089 r = dns_query_bus_track(q, message);
1090 if (r < 0)
1091 goto fail;
1092
1093 r = dns_query_go(q);
1094 if (r < 0)
1095 goto fail;
1096
2d4c5cbc 1097 return 1;
45ec7efb
LP
1098
1099fail:
1100 dns_query_free(q);
1101 return r;
2d4c5cbc
LP
1102}
1103
7f220d94
LP
1104static int append_dns_server(sd_bus_message *reply, DnsServer *s) {
1105 int r;
1106
1107 assert(reply);
1108 assert(s);
1109
1110 r = sd_bus_message_open_container(reply, 'r', "iiay");
1111 if (r < 0)
1112 return r;
1113
1114 r = sd_bus_message_append(reply, "ii", s->link ? s->link->ifindex : 0, s->family);
1115 if (r < 0)
1116 return r;
1117
1118 r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family));
1119 if (r < 0)
1120 return r;
1121
1122 return sd_bus_message_close_container(reply);
1123}
1124
1125static int bus_property_get_dns_servers(
1126 sd_bus *bus,
1127 const char *path,
1128 const char *interface,
1129 const char *property,
1130 sd_bus_message *reply,
1131 void *userdata,
1132 sd_bus_error *error) {
1133
1134 Manager *m = userdata;
1135 unsigned c = 0;
1136 DnsServer *s;
1137 Iterator i;
1138 Link *l;
1139 int r;
1140
1141 assert(reply);
1142 assert(m);
1143
1144 r = sd_bus_message_open_container(reply, 'a', "(iiay)");
1145 if (r < 0)
1146 return r;
1147
1148 LIST_FOREACH(servers, s, m->dns_servers) {
1149 r = append_dns_server(reply, s);
1150 if (r < 0)
1151 return r;
1152
1153 c++;
1154 }
1155
1156 HASHMAP_FOREACH(l, m->links, i) {
1157 LIST_FOREACH(servers, s, l->dns_servers) {
1158 r = append_dns_server(reply, s);
1159 if (r < 0)
1160 return r;
1161 c++;
1162 }
1163 }
1164
1165 if (c == 0) {
1166 LIST_FOREACH(servers, s, m->fallback_dns_servers) {
1167 r = append_dns_server(reply, s);
1168 if (r < 0)
1169 return r;
1170 }
1171 }
1172
1173 return sd_bus_message_close_container(reply);
1174}
1175
1176static int bus_property_get_search_domains(
1177 sd_bus *bus,
1178 const char *path,
1179 const char *interface,
1180 const char *property,
1181 sd_bus_message *reply,
1182 void *userdata,
1183 sd_bus_error *error) {
1184
1185 Manager *m = userdata;
1186 DnsSearchDomain *d;
1187 Iterator i;
1188 Link *l;
1189 int r;
1190
1191 assert(reply);
1192 assert(m);
1193
1194 r = sd_bus_message_open_container(reply, 'a', "(is)");
1195 if (r < 0)
1196 return r;
1197
1198 LIST_FOREACH(domains, d, m->search_domains) {
1199 r = sd_bus_message_append(reply, "(is)", 0, d->name);
1200 if (r < 0)
1201 return r;
1202 }
1203
1204 HASHMAP_FOREACH(l, m->links, i) {
1205 LIST_FOREACH(domains, d, l->search_domains) {
1206 r = sd_bus_message_append(reply, "is", l->ifindex, d->name);
1207 if (r < 0)
1208 return r;
1209 }
1210 }
1211
1212 return sd_bus_message_close_container(reply);
1213}
1214
74b2466e
LP
1215static const sd_bus_vtable resolve_vtable[] = {
1216 SD_BUS_VTABLE_START(0),
7f220d94
LP
1217 SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
1218 SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0),
1219 SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0),
1220
78c6a153
LP
1221 SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
1222 SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED),
1223 SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
45ec7efb 1224 SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
74b2466e
LP
1225 SD_BUS_VTABLE_END,
1226};
1227
1228static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) {
1229 Manager *m = userdata;
1230
1231 assert(s);
1232 assert(m);
1233
1234 m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source);
1235
1236 manager_connect_bus(m);
1237 return 0;
1238}
1239
19070062 1240static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) {
902bb5d8
LP
1241 Manager *m = userdata;
1242 int b, r;
1243
19070062
LP
1244 assert(message);
1245 assert(m);
902bb5d8
LP
1246
1247 r = sd_bus_message_read(message, "b", &b);
1248 if (r < 0) {
da927ba9 1249 log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m");
902bb5d8
LP
1250 return 0;
1251 }
1252
1253 if (b)
1254 return 0;
1255
1256 log_debug("Coming back from suspend, verifying all RRs...");
1257
1258 manager_verify_all(m);
1259 return 0;
1260}
1261
74b2466e
LP
1262int manager_connect_bus(Manager *m) {
1263 int r;
1264
1265 assert(m);
1266
1267 if (m->bus)
1268 return 0;
1269
1270 r = sd_bus_default_system(&m->bus);
1271 if (r < 0) {
1272 /* We failed to connect? Yuck, we must be in early
1273 * boot. Let's try in 5s again. As soon as we have
1274 * kdbus we can stop doing this... */
1275
da927ba9 1276 log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m");
74b2466e
LP
1277
1278 r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m);
f647962d
MS
1279 if (r < 0)
1280 return log_error_errno(r, "Failed to install bus reconnect time event: %m");
74b2466e
LP
1281
1282 return 0;
1283 }
1284
4d1cf1e2 1285 r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m);
f647962d
MS
1286 if (r < 0)
1287 return log_error_errno(r, "Failed to register object: %m");
74b2466e
LP
1288
1289 r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0);
f647962d
MS
1290 if (r < 0)
1291 return log_error_errno(r, "Failed to register name: %m");
74b2466e
LP
1292
1293 r = sd_bus_attach_event(m->bus, m->event, 0);
f647962d
MS
1294 if (r < 0)
1295 return log_error_errno(r, "Failed to attach bus to event loop: %m");
74b2466e 1296
902bb5d8
LP
1297 r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot,
1298 "type='signal',"
1299 "sender='org.freedesktop.login1',"
1300 "interface='org.freedesktop.login1.Manager',"
1301 "member='PrepareForSleep',"
1302 "path='/org/freedesktop/login1'",
1303 match_prepare_for_sleep,
1304 m);
1305 if (r < 0)
da927ba9 1306 log_error_errno(r, "Failed to add match for PrepareForSleep: %m");
902bb5d8 1307
74b2466e
LP
1308 return 0;
1309}