Coverage Report

Created: 2026-08-31 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/avahi/avahi-common/domain.c
Line
Count
Source
1
/***
2
  This file is part of avahi.
3
4
  avahi is free software; you can redistribute it and/or modify it
5
  under the terms of the GNU Lesser General Public License as
6
  published by the Free Software Foundation; either version 2.1 of the
7
  License, or (at your option) any later version.
8
9
  avahi is distributed in the hope that it will be useful, but WITHOUT
10
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12
  Public License for more details.
13
14
  You should have received a copy of the GNU Lesser General Public
15
  License along with avahi; if not, write to the Free Software
16
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17
  USA.
18
***/
19
20
#ifdef HAVE_CONFIG_H
21
#include <config.h>
22
#endif
23
24
#include <string.h>
25
#include <unistd.h>
26
#include <fcntl.h>
27
#include <errno.h>
28
#include <limits.h>
29
#include <stdio.h>
30
#include <ctype.h>
31
#include <stdlib.h>
32
#include <assert.h>
33
34
#include "domain.h"
35
#include "malloc.h"
36
#include "error.h"
37
#include "address.h"
38
#include "utf8.h"
39
40
/* Read the first label from string *name, unescape "\" and write it to dest */
41
271k
char *avahi_unescape_label(const char **name, char *dest, size_t size) {
42
271k
    unsigned i = 0;
43
271k
    char *d;
44
45
271k
    assert(dest);
46
271k
    assert(size > 0);
47
271k
    assert(name);
48
49
271k
    d = dest;
50
51
3.36M
    for (;;) {
52
3.36M
        if (i >= size)
53
58
            return NULL;
54
55
3.36M
        if (**name == '.') {
56
221k
            (*name)++;
57
221k
            break;
58
221k
        }
59
60
3.14M
        if (**name == 0)
61
48.9k
            break;
62
63
3.09M
        if (**name == '\\') {
64
            /* Escaped character */
65
66
540k
            (*name) ++;
67
68
540k
            if (**name == 0)
69
                /* Ending NUL */
70
75
                return NULL;
71
72
540k
            else if (**name == '\\' || **name == '.') {
73
                /* Escaped backslash or dot */
74
61.7k
                *(d++) = *((*name) ++);
75
61.7k
                i++;
76
478k
            } else if (isdigit((unsigned char)**name)) {
77
478k
                int n;
78
79
                /* Escaped literal ASCII character */
80
81
478k
                if (!isdigit((unsigned char)*(*name+1)) || !isdigit((unsigned char)*(*name+2)))
82
54
                    return NULL;
83
84
478k
                n = ((uint8_t) (**name - '0') * 100) + ((uint8_t) (*(*name+1) - '0') * 10) + ((uint8_t) (*(*name +2) - '0'));
85
86
478k
                if (n > 255 || n == 0)
87
111
                    return NULL;
88
89
478k
                *(d++) = (char) n;
90
478k
                i++;
91
92
478k
                (*name) += 3;
93
478k
            } else
94
200
                return NULL;
95
96
2.55M
        } else {
97
98
            /* Normal character */
99
100
2.55M
            *(d++) = *((*name) ++);
101
2.55M
            i++;
102
2.55M
        }
103
3.09M
    }
104
105
271k
    assert(i < size);
106
107
270k
    *d = 0;
108
109
270k
    if (!avahi_utf8_valid(dest))
110
747
        return NULL;
111
112
269k
    return dest;
113
270k
}
114
115
/* Escape "\" and ".", append \0 */
116
146k
char *avahi_escape_label(const char* src, size_t src_length, char **ret_name, size_t *ret_size) {
117
146k
    char *r;
118
119
146k
    assert(src);
120
146k
    assert(ret_name);
121
146k
    assert(*ret_name);
122
146k
    assert(ret_size);
123
146k
    assert(*ret_size > 0);
124
125
146k
    r = *ret_name;
126
127
1.92M
    while (src_length > 0) {
128
1.77M
        if (*src == '.' || *src == '\\') {
129
130
            /* Dot or backslash */
131
132
31.6k
            if (*ret_size < 3)
133
16
                return NULL;
134
135
31.6k
            *((*ret_name) ++) = '\\';
136
31.6k
            *((*ret_name) ++) = *src;
137
31.6k
            (*ret_size) -= 2;
138
139
1.74M
        } else if (
140
1.74M
            *src == '_' ||
141
1.73M
            *src == '-' ||
142
653k
            (*src >= '0' && *src <= '9') ||
143
389k
            (*src >= 'a' && *src <= 'z') ||
144
1.44M
            (*src >= 'A' && *src <= 'Z')) {
145
146
            /* Proper character */
147
148
1.44M
            if (*ret_size < 2)
149
16
                return NULL;
150
151
1.44M
            *((*ret_name)++) = *src;
152
1.44M
            (*ret_size) --;
153
154
1.44M
        } else {
155
156
            /* Everything else */
157
158
304k
            if (*ret_size < 5)
159
66
                return NULL;
160
161
304k
            *((*ret_name) ++) = '\\';
162
304k
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src / 100);
163
304k
            *((*ret_name) ++) = '0' + (char) (((uint8_t) *src / 10) % 10);
164
304k
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src % 10);
165
166
304k
            (*ret_size) -= 4;
167
304k
        }
168
169
1.77M
        src_length --;
170
1.77M
        src++;
171
1.77M
    }
172
173
146k
    **ret_name = 0;
174
175
146k
    return r;
176
146k
}
177
178
24.0k
char *avahi_normalize_name(const char *s, char *ret_s, size_t size) {
179
24.0k
    int empty = 1;
180
24.0k
    char *r;
181
182
24.0k
    assert(s);
183
24.0k
    assert(ret_s);
184
24.0k
    assert(size > 0);
185
186
24.0k
    r = ret_s;
187
24.0k
    *ret_s = 0;
188
189
124k
    while (*s) {
190
101k
        char label[AVAHI_LABEL_MAX];
191
192
101k
        if (!(avahi_unescape_label(&s, label, sizeof(label))))
193
764
            return NULL;
194
195
100k
        if (label[0] == 0) {
196
197
76
            if (*s == 0 && empty)
198
4
                return ret_s;
199
200
72
            return NULL;
201
76
        }
202
203
100k
        if (!empty) {
204
88.9k
            if (size < 2)
205
4
                return NULL;
206
207
88.9k
            *(r++) = '.';
208
88.9k
            size--;
209
210
88.9k
        } else
211
11.5k
            empty = 0;
212
213
100k
        if (!(avahi_escape_label(label, strlen(label), &r, &size)))
214
67
            return NULL;
215
100k
    }
216
217
23.1k
    return ret_s;
218
24.0k
}
219
220
9.18k
char *avahi_normalize_name_strdup(const char *s) {
221
9.18k
    char t[AVAHI_DOMAIN_NAME_MAX];
222
9.18k
    assert(s);
223
224
9.18k
    if (!(avahi_normalize_name(s, t, sizeof(t))))
225
388
        return NULL;
226
227
8.79k
    return avahi_strdup(t);
228
9.18k
}
229
230
6.96k
int avahi_domain_equal(const char *a, const char *b) {
231
6.96k
    assert(a);
232
6.96k
    assert(b);
233
234
6.96k
    if (a == b)
235
0
        return 1;
236
237
26.8k
    for (;;) {
238
26.8k
        char ca[AVAHI_LABEL_MAX], cb[AVAHI_LABEL_MAX], *r;
239
240
26.8k
        r = avahi_unescape_label(&a, ca, sizeof(ca));
241
26.8k
        assert(r);
242
26.8k
        r = avahi_unescape_label(&b, cb, sizeof(cb));
243
26.8k
        assert(r);
244
245
26.8k
        if (strcasecmp(ca, cb))
246
788
            return 0;
247
248
26.0k
        if (!*a && !*b)
249
6.17k
            return 1;
250
26.0k
    }
251
252
0
    return 1;
253
6.96k
}
254
255
958
int avahi_is_valid_service_type_generic(const char *t) {
256
958
    assert(t);
257
258
958
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
259
41
        return 0;
260
261
1.85k
    do {
262
1.85k
        char label[AVAHI_LABEL_MAX];
263
264
1.85k
        if (!(avahi_unescape_label(&t, label, sizeof(label))))
265
121
            return 0;
266
267
1.73k
        if (strlen(label) <= 2 || label[0] != '_')
268
687
            return 0;
269
270
1.73k
    } while (*t);
271
272
109
    return 1;
273
917
}
274
275
958
int avahi_is_valid_service_type_strict(const char *t) {
276
958
    char label[AVAHI_LABEL_MAX];
277
958
    assert(t);
278
279
958
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
280
41
        return 0;
281
282
    /* Application name */
283
284
917
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
285
84
        return 0;
286
287
833
    if (strlen(label) <= 2 || label[0] != '_')
288
567
        return 0;
289
290
266
    if (!*t)
291
13
        return 0;
292
293
    /* _tcp or _udp boilerplate */
294
295
253
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
296
9
        return 0;
297
298
244
    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
299
233
        return 0;
300
301
11
    if (*t)
302
9
        return 0;
303
304
2
    return 1;
305
11
}
306
307
958
const char *avahi_get_type_from_subtype(const char *t) {
308
958
    char label[AVAHI_LABEL_MAX];
309
958
    const char *ret;
310
958
    assert(t);
311
312
958
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
313
41
        return NULL;
314
315
    /* Subtype name */
316
317
917
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
318
84
        return NULL;
319
320
833
    if (strlen(label) <= 2 || label[0] != '_')
321
567
        return NULL;
322
323
266
    if (!*t)
324
13
        return NULL;
325
326
    /* String "_sub" */
327
328
253
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
329
9
        return NULL;
330
331
244
    if (strcasecmp(label, "_sub"))
332
134
        return NULL;
333
334
110
    if (!*t)
335
1
        return NULL;
336
337
109
    ret = t;
338
339
    /* Application name */
340
341
109
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
342
3
        return NULL;
343
344
106
    if (strlen(label) <= 2 || label[0] != '_')
345
17
        return NULL;
346
347
89
    if (!*t)
348
4
        return NULL;
349
350
    /* _tcp or _udp boilerplate */
351
352
85
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
353
3
        return NULL;
354
355
82
    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
356
71
        return NULL;
357
358
11
    if (*t)
359
9
        return NULL;
360
361
2
    return ret;
362
11
}
363
364
958
int avahi_is_valid_service_subtype(const char *t) {
365
958
    assert(t);
366
367
958
    return !!avahi_get_type_from_subtype(t);
368
958
}
369
370
14.6k
int avahi_is_valid_domain_name(const char *t) {
371
14.6k
    int is_first = 1;
372
14.6k
    char normalized[AVAHI_DOMAIN_NAME_MAX];
373
14.6k
    assert(t);
374
375
14.6k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
376
39
        return 0;
377
378
14.6k
    if (!(avahi_normalize_name(t, normalized, sizeof(normalized))))
379
519
        return 0;
380
381
67.1k
    do {
382
67.1k
        char label[AVAHI_LABEL_MAX];
383
384
67.1k
        if (!(avahi_unescape_label(&t, label, sizeof(label))))
385
0
            return 0;
386
387
        /* Explicitly allow the root domain name */
388
67.1k
        if (is_first && label[0] == 0 && *t == 0)
389
7.09k
            return 1;
390
391
60.1k
        is_first = 0;
392
393
60.1k
        if (label[0] == 0)
394
0
            return 0;
395
396
60.1k
    } while (*t);
397
398
7.03k
    return 1;
399
14.1k
}
400
401
2.64k
int avahi_is_valid_service_name(const char *t) {
402
2.64k
    assert(t);
403
404
2.64k
    if (strlen(t) >= AVAHI_LABEL_MAX || !*t || !avahi_utf8_valid(t))
405
452
        return 0;
406
407
2.19k
    return 1;
408
2.64k
}
409
410
2.36k
int avahi_is_valid_host_name(const char *t) {
411
2.36k
    char label[AVAHI_LABEL_MAX];
412
2.36k
    assert(t);
413
414
2.36k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
415
82
        return 0;
416
417
2.27k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
418
168
        return 0;
419
420
2.11k
    if (strlen(label) < 1)
421
26
        return 0;
422
423
2.08k
    if (*t)
424
752
        return 0;
425
426
1.33k
    return 1;
427
2.08k
}
428
429
623
unsigned avahi_domain_hash(const char *s) {
430
623
    unsigned hash = 0;
431
432
6.83k
    while (*s) {
433
6.20k
        char c[AVAHI_LABEL_MAX], *p, *r;
434
435
6.20k
        r = avahi_unescape_label(&s, c, sizeof(c));
436
6.20k
        assert(r);
437
438
40.6k
        for (p = c; *p; p++)
439
34.3k
            hash = 31 * hash + tolower((unsigned char)*p);
440
6.20k
    }
441
442
623
    return hash;
443
623
}
444
445
0
int avahi_service_name_join(char *p, size_t size, const char *name, const char *type, const char *domain) {
446
0
    char escaped_name[AVAHI_LABEL_MAX*4];
447
0
    char normalized_type[AVAHI_DOMAIN_NAME_MAX];
448
0
    char normalized_domain[AVAHI_DOMAIN_NAME_MAX];
449
450
0
    assert(p);
451
452
    /* Validity checks */
453
454
0
    if ((name && !avahi_is_valid_service_name(name)))
455
0
        return AVAHI_ERR_INVALID_SERVICE_NAME;
456
457
0
    if (!avahi_is_valid_service_type_generic(type))
458
0
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
459
460
0
    if (!avahi_is_valid_domain_name(domain))
461
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
462
463
    /* Preparation */
464
465
0
    if (name) {
466
0
        size_t l = sizeof(escaped_name);
467
0
        char *e = escaped_name, *r;
468
0
        r = avahi_escape_label(name, strlen(name), &e, &l);
469
0
        assert(r);
470
0
    }
471
472
0
    if (!(avahi_normalize_name(type, normalized_type, sizeof(normalized_type))))
473
0
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
474
475
0
    if (!(avahi_normalize_name(domain, normalized_domain, sizeof(normalized_domain))))
476
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
477
478
    /* Concatenation */
479
480
0
    snprintf(p, size, "%s%s%s.%s", name ? escaped_name : "", name ? "." : "", normalized_type, normalized_domain);
481
482
0
    return AVAHI_OK;
483
0
}
484
485
#ifndef HAVE_STRLCPY
486
487
static size_t strlcpy(char *dest, const char *src, size_t n) {
488
    assert(dest);
489
    assert(src);
490
491
    if (n > 0) {
492
        strncpy(dest, src, n-1);
493
        dest[n-1] = 0;
494
    }
495
496
    return strlen(src);
497
}
498
499
#endif
500
501
0
int avahi_service_name_split(const char *p, char *name, size_t name_size, char *type, size_t type_size, char *domain, size_t domain_size) {
502
0
    enum {
503
0
        NAME,
504
0
        TYPE,
505
0
        DOMAIN
506
0
    } state;
507
0
    int type_empty = 1, domain_empty = 1;
508
0
    char *oname, *otype, *odomain;
509
510
0
    assert(p);
511
0
    assert(type);
512
0
    assert(type_size > 0);
513
0
    assert(domain);
514
0
    assert(domain_size > 0);
515
516
0
    oname = name;
517
0
    otype = type;
518
0
    odomain = domain;
519
520
0
    if (name) {
521
0
        assert(name_size > 0);
522
0
        *name = 0;
523
0
        state = NAME;
524
0
    } else
525
0
        state = TYPE;
526
527
0
    *type = *domain = 0;
528
529
0
    while (*p) {
530
0
        char buf[64];
531
532
0
        if (!(avahi_unescape_label(&p, buf, sizeof(buf))))
533
0
            return -1;
534
535
0
        switch (state) {
536
0
            case NAME:
537
0
                strlcpy(name, buf, name_size);
538
0
                state = TYPE;
539
0
                break;
540
541
0
            case TYPE:
542
543
0
                if (buf[0] == '_') {
544
545
0
                    if (!type_empty) {
546
0
                        if (!type_size)
547
0
                            return AVAHI_ERR_NO_MEMORY;
548
549
0
                        *(type++) = '.';
550
0
                        type_size --;
551
552
0
                    } else
553
0
                        type_empty = 0;
554
555
0
                    if (!(avahi_escape_label(buf, strlen(buf), &type, &type_size)))
556
0
                        return AVAHI_ERR_NO_MEMORY;
557
558
0
                    break;
559
0
                }
560
561
0
                state = DOMAIN;
562
                /* fall through */
563
564
0
            case DOMAIN:
565
566
0
                if (!domain_empty) {
567
0
                    if (!domain_size)
568
0
                        return AVAHI_ERR_NO_MEMORY;
569
570
0
                    *(domain++) = '.';
571
0
                    domain_size --;
572
0
                } else
573
0
                    domain_empty = 0;
574
575
0
                if (!(avahi_escape_label(buf, strlen(buf), &domain, &domain_size)))
576
0
                    return AVAHI_ERR_NO_MEMORY;
577
578
0
                break;
579
0
        }
580
0
    }
581
582
0
    if ((oname && !avahi_is_valid_service_name(oname)))
583
0
        return AVAHI_ERR_INVALID_SERVICE_NAME;
584
585
0
    if (!avahi_is_valid_service_type_strict(otype) && !avahi_is_valid_service_subtype(otype))
586
0
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
587
588
0
    if (!avahi_is_valid_domain_name(odomain))
589
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
590
591
0
    return 0;
592
0
}
593
594
958
int avahi_is_valid_fqdn(const char *t) {
595
958
    char label[AVAHI_LABEL_MAX];
596
958
    char normalized[AVAHI_DOMAIN_NAME_MAX];
597
958
    const char *k = t;
598
958
    AvahiAddress a;
599
958
    assert(t);
600
601
958
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
602
39
        return 0;
603
604
919
    if (!avahi_is_valid_domain_name(t))
605
211
        return 0;
606
607
    /* Check if there are at least two labels*/
608
708
    if (!(avahi_unescape_label(&k, label, sizeof(label))))
609
0
        return 0;
610
611
708
    if (label[0] == 0 || !k)
612
3
        return 0;
613
614
705
    if (!(avahi_unescape_label(&k, label, sizeof(label))))
615
0
        return 0;
616
617
705
    if (label[0] == 0 || !k)
618
444
        return 0;
619
620
    /* Make sure that the name is not an IP address */
621
261
    if (!(avahi_normalize_name(t, normalized, sizeof(normalized))))
622
0
        return 0;
623
624
261
    if (avahi_address_parse(normalized, AVAHI_PROTO_UNSPEC, &a))
625
1
        return 0;
626
627
260
    return 1;
628
261
}