1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "conf-files.h"
4 #include "conf-parser.h"
5 #include "def.h"
6 #include "resolved-dnssd.h"
7 #include "resolved-dns-rr.h"
8 #include "resolved-manager.h"
9 #include "resolved-conf.h"
10 #include "specifier.h"
11 #include "strv.h"
12 
13 #define DNSSD_SERVICE_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/dnssd"))
14 
dnssd_txtdata_free(DnssdTxtData * txt_data)15 DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data) {
16         if (!txt_data)
17                 return NULL;
18 
19         dns_resource_record_unref(txt_data->rr);
20         dns_txt_item_free_all(txt_data->txt);
21 
22         return mfree(txt_data);
23 }
24 
dnssd_txtdata_free_all(DnssdTxtData * txt_data)25 DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data) {
26         DnssdTxtData *next;
27 
28         if (!txt_data)
29                 return NULL;
30 
31         next = txt_data->items_next;
32 
33         dnssd_txtdata_free(txt_data);
34 
35         return dnssd_txtdata_free_all(next);
36 }
37 
dnssd_service_free(DnssdService * service)38 DnssdService *dnssd_service_free(DnssdService *service) {
39         if (!service)
40                 return NULL;
41 
42         if (service->manager)
43                 hashmap_remove(service->manager->dnssd_services, service->name);
44 
45         dns_resource_record_unref(service->ptr_rr);
46         dns_resource_record_unref(service->srv_rr);
47 
48         dnssd_txtdata_free_all(service->txt_data_items);
49 
50         free(service->filename);
51         free(service->name);
52         free(service->type);
53         free(service->name_template);
54 
55         return mfree(service);
56 }
57 
dnssd_service_load(Manager * manager,const char * filename)58 static int dnssd_service_load(Manager *manager, const char *filename) {
59         _cleanup_(dnssd_service_freep) DnssdService *service = NULL;
60         _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
61         char *d;
62         const char *dropin_dirname;
63         int r;
64 
65         assert(manager);
66         assert(filename);
67 
68         service = new0(DnssdService, 1);
69         if (!service)
70                 return log_oom();
71 
72         service->filename = strdup(filename);
73         if (!service->filename)
74                 return log_oom();
75 
76         service->name = strdup(basename(filename));
77         if (!service->name)
78                 return log_oom();
79 
80         d = endswith(service->name, ".dnssd");
81         if (!d)
82                 return -EINVAL;
83 
84         assert(streq(d, ".dnssd"));
85 
86         *d = '\0';
87 
88         dropin_dirname = strjoina(service->name, ".dnssd.d");
89 
90         r = config_parse_many(
91                         STRV_MAKE_CONST(filename), DNSSD_SERVICE_DIRS, dropin_dirname,
92                         "Service\0",
93                         config_item_perf_lookup, resolved_dnssd_gperf_lookup,
94                         CONFIG_PARSE_WARN,
95                         service,
96                         NULL);
97         if (r < 0)
98                 return r;
99 
100         if (!service->name_template)
101                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
102                                        "%s doesn't define service instance name",
103                                        service->name);
104 
105         if (!service->type)
106                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
107                                        "%s doesn't define service type",
108                                        service->name);
109 
110         if (LIST_IS_EMPTY(service->txt_data_items)) {
111                 txt_data = new0(DnssdTxtData, 1);
112                 if (!txt_data)
113                         return log_oom();
114 
115                 r = dns_txt_item_new_empty(&txt_data->txt);
116                 if (r < 0)
117                         return r;
118 
119                 LIST_PREPEND(items, service->txt_data_items, txt_data);
120                 TAKE_PTR(txt_data);
121         }
122 
123         r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->name, service);
124         if (r < 0)
125                 return r;
126 
127         service->manager = manager;
128 
129         r = dnssd_update_rrs(service);
130         if (r < 0)
131                 return r;
132 
133         TAKE_PTR(service);
134 
135         return 0;
136 }
137 
specifier_dnssd_hostname(char specifier,const void * data,const char * root,const void * userdata,char ** ret)138 static int specifier_dnssd_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
139         const Manager *m = ASSERT_PTR(userdata);
140         char *n;
141 
142         assert(m->llmnr_hostname);
143 
144         n = strdup(m->llmnr_hostname);
145         if (!n)
146                 return -ENOMEM;
147 
148         *ret = n;
149         return 0;
150 }
151 
dnssd_render_instance_name(Manager * m,DnssdService * s,char ** ret)152 int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret) {
153         static const Specifier specifier_table[] = {
154                 { 'a', specifier_architecture,   NULL },
155                 { 'b', specifier_boot_id,        NULL },
156                 { 'B', specifier_os_build_id,    NULL },
157                 { 'H', specifier_dnssd_hostname, NULL },
158                 { 'm', specifier_machine_id,     NULL },
159                 { 'o', specifier_os_id,          NULL },
160                 { 'v', specifier_kernel_release, NULL },
161                 { 'w', specifier_os_version_id,  NULL },
162                 { 'W', specifier_os_variant_id,  NULL },
163                 {}
164         };
165         _cleanup_free_ char *name = NULL;
166         int r;
167 
168         assert(m);
169         assert(s);
170         assert(s->name_template);
171 
172         r = specifier_printf(s->name_template, DNS_LABEL_MAX, specifier_table, NULL, m, &name);
173         if (r < 0)
174                 return log_debug_errno(r, "Failed to replace specifiers: %m");
175 
176         if (!dns_service_name_is_valid(name))
177                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
178                                        "Service instance name '%s' is invalid.",
179                                        name);
180 
181         if (ret)
182                 *ret = TAKE_PTR(name);
183 
184         return 0;
185 }
186 
dnssd_load(Manager * manager)187 int dnssd_load(Manager *manager) {
188         _cleanup_strv_free_ char **files = NULL;
189         int r;
190 
191         assert(manager);
192 
193         if (manager->mdns_support != RESOLVE_SUPPORT_YES)
194                 return 0;
195 
196         r = conf_files_list_strv(&files, ".dnssd", NULL, 0, DNSSD_SERVICE_DIRS);
197         if (r < 0)
198                 return log_error_errno(r, "Failed to enumerate .dnssd files: %m");
199 
200         STRV_FOREACH_BACKWARDS(f, files) {
201                 r = dnssd_service_load(manager, *f);
202                 if (r < 0)
203                         log_warning_errno(r, "Failed to load '%s': %m", *f);;
204         }
205 
206         return 0;
207 }
208 
dnssd_update_rrs(DnssdService * s)209 int dnssd_update_rrs(DnssdService *s) {
210         _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL;
211         int r;
212 
213         assert(s);
214         assert(s->txt_data_items);
215         assert(s->manager);
216 
217         s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
218         s->srv_rr = dns_resource_record_unref(s->srv_rr);
219         LIST_FOREACH(items, txt_data, s->txt_data_items)
220                 txt_data->rr = dns_resource_record_unref(txt_data->rr);
221 
222         r = dnssd_render_instance_name(s->manager, s, &n);
223         if (r < 0)
224                 return r;
225 
226         r = dns_name_concat(s->type, "local", 0, &service_name);
227         if (r < 0)
228                 return r;
229         r = dns_name_concat(n, service_name, 0, &full_name);
230         if (r < 0)
231                 return r;
232 
233         LIST_FOREACH(items, txt_data, s->txt_data_items) {
234                 txt_data->rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT,
235                                                             full_name);
236                 if (!txt_data->rr)
237                         goto oom;
238 
239                 txt_data->rr->ttl = MDNS_DEFAULT_TTL;
240                 txt_data->rr->txt.items = dns_txt_item_copy(txt_data->txt);
241                 if (!txt_data->rr->txt.items)
242                         goto oom;
243         }
244 
245         s->ptr_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR,
246                                                  service_name);
247         if (!s->ptr_rr)
248                 goto oom;
249 
250         s->ptr_rr->ttl = MDNS_DEFAULT_TTL;
251         s->ptr_rr->ptr.name = strdup(full_name);
252         if (!s->ptr_rr->ptr.name)
253                 goto oom;
254 
255         s->srv_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SRV,
256                                                  full_name);
257         if (!s->srv_rr)
258                 goto oom;
259 
260         s->srv_rr->ttl = MDNS_DEFAULT_TTL;
261         s->srv_rr->srv.priority = s->priority;
262         s->srv_rr->srv.weight = s->weight;
263         s->srv_rr->srv.port = s->port;
264         s->srv_rr->srv.name = strdup(s->manager->mdns_hostname);
265         if (!s->srv_rr->srv.name)
266                 goto oom;
267 
268         return 0;
269 
270 oom:
271         LIST_FOREACH(items, txt_data, s->txt_data_items)
272                 txt_data->rr = dns_resource_record_unref(txt_data->rr);
273         s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
274         s->srv_rr = dns_resource_record_unref(s->srv_rr);
275         return -ENOMEM;
276 }
277 
dnssd_txt_item_new_from_string(const char * key,const char * value,DnsTxtItem ** ret_item)278 int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item) {
279         size_t length;
280         DnsTxtItem *i;
281 
282         length = strlen(key);
283 
284         if (!isempty(value))
285                 length += strlen(value) + 1; /* length of value plus '=' */
286 
287         i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
288         if (!i)
289                 return -ENOMEM;
290 
291         memcpy(i->data, key, strlen(key));
292         if (!isempty(value)) {
293                 memcpy(i->data + strlen(key), "=", 1);
294                 memcpy(i->data + strlen(key) + 1, value, strlen(value));
295         }
296         i->length = length;
297 
298         *ret_item = TAKE_PTR(i);
299 
300         return 0;
301 }
302 
dnssd_txt_item_new_from_data(const char * key,const void * data,const size_t size,DnsTxtItem ** ret_item)303 int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t size, DnsTxtItem **ret_item) {
304         size_t length;
305         DnsTxtItem *i;
306 
307         length = strlen(key);
308 
309         if (size > 0)
310                 length += size + 1; /* size of date plus '=' */
311 
312         i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
313         if (!i)
314                 return -ENOMEM;
315 
316         memcpy(i->data, key, strlen(key));
317         if (size > 0) {
318                 memcpy(i->data + strlen(key), "=", 1);
319                 memcpy(i->data + strlen(key) + 1, data, size);
320         }
321         i->length = length;
322 
323         *ret_item = TAKE_PTR(i);
324 
325         return 0;
326 }
327 
dnssd_signal_conflict(Manager * manager,const char * name)328 int dnssd_signal_conflict(Manager *manager, const char *name) {
329         DnssdService *s;
330         int r;
331 
332         if (sd_bus_is_ready(manager->bus) <= 0)
333                 return 0;
334 
335         HASHMAP_FOREACH(s, manager->dnssd_services) {
336                 if (s->withdrawn)
337                         continue;
338 
339                 if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name)) {
340                         _cleanup_free_ char *path = NULL;
341 
342                         s->withdrawn = true;
343 
344                         r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path);
345                         if (r < 0)
346                                 return log_error_errno(r, "Can't get D-BUS object path: %m");
347 
348                         r = sd_bus_emit_signal(manager->bus,
349                                                path,
350                                                "org.freedesktop.resolve1.DnssdService",
351                                                "Conflicted",
352                                                NULL);
353                         if (r < 0)
354                                 return log_error_errno(r, "Cannot emit signal: %m");
355 
356                         break;
357                 }
358         }
359 
360         return 0;
361 }
362