]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dnssd.c
Merge pull request #8552 from keszybz/test-improvements
[thirdparty/systemd.git] / src / resolve / resolved-dnssd.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2017 Dmitry Rozhkov
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "conf-files.h"
21 #include "conf-parser.h"
22 #include "resolved-dnssd.h"
23 #include "resolved-dns-rr.h"
24 #include "resolved-manager.h"
25 #include "specifier.h"
26 #include "strv.h"
27
28 const char* const dnssd_service_dirs[] = {
29 "/etc/systemd/dnssd",
30 "/run/systemd/dnssd",
31 "/usr/lib/systemd/dnssd",
32 #ifdef HAVE_SPLIT_USR
33 "/lib/systemd/dnssd",
34 #endif
35 NULL
36 };
37
38 DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data) {
39 if (!txt_data)
40 return NULL;
41
42 dns_resource_record_unref(txt_data->rr);
43 dns_txt_item_free_all(txt_data->txt);
44
45 return mfree(txt_data);
46 }
47
48 DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data) {
49 DnssdTxtData *next;
50
51 if (!txt_data)
52 return NULL;
53
54 next = txt_data->items_next;
55
56 dnssd_txtdata_free(txt_data);
57
58 return dnssd_txtdata_free_all(next);
59 }
60
61 DnssdService *dnssd_service_free(DnssdService *service) {
62 if (!service)
63 return NULL;
64
65 if (service->manager)
66 hashmap_remove(service->manager->dnssd_services, service->name);
67
68 dns_resource_record_unref(service->ptr_rr);
69 dns_resource_record_unref(service->srv_rr);
70
71 dnssd_txtdata_free_all(service->txt_data_items);
72
73 free(service->filename);
74 free(service->name);
75 free(service->type);
76 free(service->name_template);
77
78 return mfree(service);
79 }
80
81 static int dnssd_service_load(Manager *manager, const char *filename) {
82 _cleanup_(dnssd_service_freep) DnssdService *service = NULL;
83 _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
84 char *d;
85 const char *dropin_dirname;
86 int r;
87
88 assert(manager);
89 assert(filename);
90
91 service = new0(DnssdService, 1);
92 if (!service)
93 return log_oom();
94
95 service->filename = strdup(filename);
96 if (!service->filename)
97 return log_oom();
98
99 service->name = strdup(basename(filename));
100 if (!service->name)
101 return log_oom();
102
103 d = endswith(service->name, ".dnssd");
104 if (!d)
105 return -EINVAL;
106
107 assert(streq(d, ".dnssd"));
108
109 *d = '\0';
110
111 dropin_dirname = strjoina(service->name, ".dnssd.d");
112
113 r = config_parse_many(filename, dnssd_service_dirs, dropin_dirname,
114 "Service\0",
115 config_item_perf_lookup, resolved_dnssd_gperf_lookup,
116 false, service);
117 if (r < 0)
118 return r;
119
120 if (!service->name_template) {
121 log_error("%s doesn't define service instance name", service->name);
122 return -EINVAL;
123 }
124
125 if (!service->type) {
126 log_error("%s doesn't define service type", service->name);
127 return -EINVAL;
128 }
129
130 if (LIST_IS_EMPTY(service->txt_data_items)) {
131 txt_data = new0(DnssdTxtData, 1);
132 if (!txt_data)
133 return log_oom();
134
135 r = dns_txt_item_new_empty(&txt_data->txt);
136 if (r < 0)
137 return r;
138
139 LIST_PREPEND(items, service->txt_data_items, txt_data);
140 txt_data = NULL;
141 }
142
143 r = hashmap_ensure_allocated(&manager->dnssd_services, &string_hash_ops);
144 if (r < 0)
145 return r;
146
147 r = hashmap_put(manager->dnssd_services, service->name, service);
148 if (r < 0)
149 return r;
150
151 service->manager = manager;
152
153 r = dnssd_update_rrs(service);
154 if (r < 0)
155 return r;
156
157 service = NULL;
158
159 return 0;
160 }
161
162 static int specifier_dnssd_host_name(char specifier, void *data, void *userdata, char **ret) {
163 DnssdService *s = (DnssdService *) userdata;
164 char *n;
165
166 assert(s);
167 assert(s->manager);
168 assert(s->manager->llmnr_hostname);
169
170 n = strdup(s->manager->llmnr_hostname);
171 if (!n)
172 return -ENOMEM;
173
174 *ret = n;
175 return 0;
176 }
177
178 int dnssd_render_instance_name(DnssdService *s, char **ret_name) {
179 static const Specifier specifier_table[] = {
180 { 'b', specifier_boot_id, NULL },
181 { 'H', specifier_dnssd_host_name, NULL },
182 { 'm', specifier_machine_id, NULL },
183 { 'v', specifier_kernel_release, NULL },
184 {}
185 };
186 _cleanup_free_ char *name = NULL;
187 int r;
188
189 assert(s);
190 assert(s->name_template);
191
192 r = specifier_printf(s->name_template, specifier_table, s, &name);
193 if (r < 0)
194 return log_debug_errno(r, "Failed to replace specifiers: %m");
195
196 if (!dns_service_name_is_valid(name)) {
197 log_debug("Service instance name '%s' is invalid.", name);
198 return -EINVAL;
199 }
200
201 *ret_name = TAKE_PTR(name);
202
203 return 0;
204 }
205
206 int dnssd_load(Manager *manager) {
207 _cleanup_strv_free_ char **files = NULL;
208 char **f;
209 int r;
210
211 assert(manager);
212
213 if (manager->mdns_support != RESOLVE_SUPPORT_YES)
214 return 0;
215
216 r = conf_files_list_strv(&files, ".dnssd", NULL, 0, dnssd_service_dirs);
217 if (r < 0)
218 return log_error_errno(r, "Failed to enumerate .dnssd files: %m");
219
220 STRV_FOREACH_BACKWARDS(f, files) {
221 r = dnssd_service_load(manager, *f);
222 if (r < 0)
223 log_warning_errno(r, "Failed to load '%s': %m", *f);;
224 }
225
226 return 0;
227 }
228
229 int dnssd_update_rrs(DnssdService *s) {
230 _cleanup_free_ char *n = NULL;
231 _cleanup_free_ char *service_name = NULL;
232 _cleanup_free_ char *full_name = NULL;
233 DnssdTxtData *txt_data;
234 int r;
235
236 assert(s);
237 assert(s->txt_data_items);
238 assert(s->manager);
239
240 s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
241 s->srv_rr = dns_resource_record_unref(s->srv_rr);
242 LIST_FOREACH(items, txt_data, s->txt_data_items)
243 txt_data->rr = dns_resource_record_unref(txt_data->rr);
244
245 r = dnssd_render_instance_name(s, &n);
246 if (r < 0)
247 return r;
248
249 r = dns_name_concat(s->type, "local", &service_name);
250 if (r < 0)
251 return r;
252 r = dns_name_concat(n, service_name, &full_name);
253 if (r < 0)
254 return r;
255
256 LIST_FOREACH(items, txt_data, s->txt_data_items) {
257 txt_data->rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT,
258 full_name);
259 if (!txt_data->rr)
260 goto oom;
261
262 txt_data->rr->ttl = MDNS_DEFAULT_TTL;
263 txt_data->rr->txt.items = dns_txt_item_copy(txt_data->txt);
264 if (!txt_data->rr->txt.items)
265 goto oom;
266 }
267
268 s->ptr_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR,
269 service_name);
270 if (!s->ptr_rr)
271 goto oom;
272
273 s->ptr_rr->ttl = MDNS_DEFAULT_TTL;
274 s->ptr_rr->ptr.name = strdup(full_name);
275 if (!s->ptr_rr->ptr.name)
276 goto oom;
277
278 s->srv_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SRV,
279 full_name);
280 if (!s->srv_rr)
281 goto oom;
282
283 s->srv_rr->ttl = MDNS_DEFAULT_TTL;
284 s->srv_rr->srv.priority = s->priority;
285 s->srv_rr->srv.weight = s->weight;
286 s->srv_rr->srv.port = s->port;
287 s->srv_rr->srv.name = strdup(s->manager->mdns_hostname);
288 if (!s->srv_rr->srv.name)
289 goto oom;
290
291 return 0;
292
293 oom:
294 LIST_FOREACH(items, txt_data, s->txt_data_items)
295 txt_data->rr = dns_resource_record_unref(txt_data->rr);
296 s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
297 s->srv_rr = dns_resource_record_unref(s->srv_rr);
298 return -ENOMEM;
299 }
300
301 int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item) {
302 size_t length;
303 DnsTxtItem *i;
304
305 length = strlen(key);
306
307 if (!isempty(value))
308 length += strlen(value) + 1; /* length of value plus '=' */
309
310 i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
311 if (!i)
312 return -ENOMEM;
313
314 memcpy(i->data, key, strlen(key));
315 if (!isempty(value)) {
316 memcpy(i->data + strlen(key), "=", 1);
317 memcpy(i->data + strlen(key) + 1, value, strlen(value));
318 }
319 i->length = length;
320
321 *ret_item = TAKE_PTR(i);
322
323 return 0;
324 }
325
326 int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t size, DnsTxtItem **ret_item) {
327 size_t length;
328 DnsTxtItem *i;
329
330 length = strlen(key);
331
332 if (size > 0)
333 length += size + 1; /* size of date plus '=' */
334
335 i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
336 if (!i)
337 return -ENOMEM;
338
339 memcpy(i->data, key, strlen(key));
340 if (size > 0) {
341 memcpy(i->data + strlen(key), "=", 1);
342 memcpy(i->data + strlen(key) + 1, data, size);
343 }
344 i->length = length;
345
346 *ret_item = TAKE_PTR(i);
347
348 return 0;
349 }
350
351 void dnssd_signal_conflict(Manager *manager, const char *name) {
352 Iterator i;
353 DnssdService *s;
354 int r;
355
356 HASHMAP_FOREACH(s, manager->dnssd_services, i) {
357 if (s->withdrawn)
358 continue;
359
360 if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name)) {
361 _cleanup_free_ char *path = NULL;
362
363 s->withdrawn = true;
364
365 r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path);
366 if (r < 0) {
367 log_error_errno(r, "Can't get D-BUS object path: %m");
368 return;
369 }
370
371 r = sd_bus_emit_signal(manager->bus,
372 path,
373 "org.freedesktop.resolve1.DnssdService",
374 "Conflicted",
375 NULL);
376 if (r < 0) {
377 log_error_errno(r, "Cannot emit signal: %m");
378 return;
379 }
380
381 break;
382 }
383 }
384 }