Coverage Report

Created: 2025-11-11 06:31

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
1.71M
char *avahi_unescape_label(const char **name, char *dest, size_t size) {
42
1.71M
    unsigned i = 0;
43
1.71M
    char *d;
44
45
1.71M
    assert(dest);
46
1.71M
    assert(size > 0);
47
1.71M
    assert(name);
48
49
1.71M
    d = dest;
50
51
11.5M
    for (;;) {
52
11.5M
        if (i >= size)
53
72
            return NULL;
54
55
11.5M
        if (**name == '.') {
56
1.33M
            (*name)++;
57
1.33M
            break;
58
1.33M
        }
59
60
10.1M
        if (**name == 0)
61
380k
            break;
62
63
9.79M
        if (**name == '\\') {
64
            /* Escaped character */
65
66
4.29M
            (*name) ++;
67
68
4.29M
            if (**name == 0)
69
                /* Ending NUL */
70
71
                return NULL;
71
72
4.29M
            else if (**name == '\\' || **name == '.') {
73
                /* Escaped backslash or dot */
74
527k
                *(d++) = *((*name) ++);
75
527k
                i++;
76
3.76M
            } else if (isdigit(**name)) {
77
3.76M
                int n;
78
79
                /* Escaped literal ASCII character */
80
81
3.76M
                if (!isdigit(*(*name+1)) || !isdigit(*(*name+2)))
82
51
                    return NULL;
83
84
3.76M
                n = ((uint8_t) (**name - '0') * 100) + ((uint8_t) (*(*name+1) - '0') * 10) + ((uint8_t) (*(*name +2) - '0'));
85
86
3.76M
                if (n > 255 || n == 0)
87
111
                    return NULL;
88
89
3.76M
                *(d++) = (char) n;
90
3.76M
                i++;
91
92
3.76M
                (*name) += 3;
93
3.76M
            } else
94
224
                return NULL;
95
96
5.49M
        } else {
97
98
            /* Normal character */
99
100
5.49M
            *(d++) = *((*name) ++);
101
5.49M
            i++;
102
5.49M
        }
103
9.79M
    }
104
105
1.71M
    assert(i < size);
106
107
1.71M
    *d = 0;
108
109
1.71M
    if (!avahi_utf8_valid(dest))
110
830
        return NULL;
111
112
1.71M
    return dest;
113
1.71M
}
114
115
/* Escape "\" and ".", append \0 */
116
365k
char *avahi_escape_label(const char* src, size_t src_length, char **ret_name, size_t *ret_size) {
117
365k
    char *r;
118
119
365k
    assert(src);
120
365k
    assert(ret_name);
121
365k
    assert(*ret_name);
122
365k
    assert(ret_size);
123
365k
    assert(*ret_size > 0);
124
125
365k
    r = *ret_name;
126
127
2.89M
    while (src_length > 0) {
128
2.52M
        if (*src == '.' || *src == '\\') {
129
130
            /* Dot or backslash */
131
132
129k
            if (*ret_size < 3)
133
16
                return NULL;
134
135
129k
            *((*ret_name) ++) = '\\';
136
129k
            *((*ret_name) ++) = *src;
137
129k
            (*ret_size) -= 2;
138
139
2.39M
        } else if (
140
2.39M
            *src == '_' ||
141
2.35M
            *src == '-' ||
142
1.79M
            (*src >= '0' && *src <= '9') ||
143
1.18M
            (*src >= 'a' && *src <= 'z') ||
144
1.41M
            (*src >= 'A' && *src <= 'Z')) {
145
146
            /* Proper character */
147
148
1.41M
            if (*ret_size < 2)
149
7
                return NULL;
150
151
1.41M
            *((*ret_name)++) = *src;
152
1.41M
            (*ret_size) --;
153
154
1.41M
        } else {
155
156
            /* Everything else */
157
158
979k
            if (*ret_size < 5)
159
60
                return NULL;
160
161
979k
            *((*ret_name) ++) = '\\';
162
979k
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src / 100);
163
979k
            *((*ret_name) ++) = '0' + (char) (((uint8_t) *src / 10) % 10);
164
979k
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src % 10);
165
166
979k
            (*ret_size) -= 4;
167
979k
        }
168
169
2.52M
        src_length --;
170
2.52M
        src++;
171
2.52M
    }
172
173
365k
    **ret_name = 0;
174
175
365k
    return r;
176
365k
}
177
178
139k
char *avahi_normalize_name(const char *s, char *ret_s, size_t size) {
179
139k
    int empty = 1;
180
139k
    char *r;
181
182
139k
    assert(s);
183
139k
    assert(ret_s);
184
139k
    assert(size > 0);
185
186
139k
    r = ret_s;
187
139k
    *ret_s = 0;
188
189
294k
    while (*s) {
190
155k
        char label[AVAHI_LABEL_MAX];
191
192
155k
        if (!(avahi_unescape_label(&s, label, sizeof(label))))
193
393
            return NULL;
194
195
155k
        if (label[0] == 0) {
196
197
22
            if (*s == 0 && empty)
198
1
                return ret_s;
199
200
21
            return NULL;
201
22
        }
202
203
155k
        if (!empty) {
204
139k
            if (size < 2)
205
5
                return NULL;
206
207
139k
            *(r++) = '.';
208
139k
            size--;
209
210
139k
        } else
211
16.0k
            empty = 0;
212
213
155k
        if (!(avahi_escape_label(label, strlen(label), &r, &size)))
214
29
            return NULL;
215
155k
    }
216
217
138k
    return ret_s;
218
139k
}
219
220
135k
char *avahi_normalize_name_strdup(const char *s) {
221
135k
    char t[AVAHI_DOMAIN_NAME_MAX];
222
135k
    assert(s);
223
224
135k
    if (!(avahi_normalize_name(s, t, sizeof(t))))
225
439
        return NULL;
226
227
135k
    return avahi_strdup(t);
228
135k
}
229
230
336k
int avahi_domain_equal(const char *a, const char *b) {
231
336k
    assert(a);
232
336k
    assert(b);
233
234
336k
    if (a == b)
235
0
        return 1;
236
237
358k
    for (;;) {
238
358k
        char ca[AVAHI_LABEL_MAX], cb[AVAHI_LABEL_MAX], *r;
239
240
358k
        r = avahi_unescape_label(&a, ca, sizeof(ca));
241
358k
        assert(r);
242
358k
        r = avahi_unescape_label(&b, cb, sizeof(cb));
243
358k
        assert(r);
244
245
358k
        if (strcasecmp(ca, cb))
246
331k
            return 0;
247
248
27.3k
        if (!*a && !*b)
249
5.40k
            return 1;
250
27.3k
    }
251
252
0
    return 1;
253
336k
}
254
255
2.69k
int avahi_is_valid_service_type_generic(const char *t) {
256
2.69k
    assert(t);
257
258
2.69k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
259
45
        return 0;
260
261
6.50k
    do {
262
6.50k
        char label[AVAHI_LABEL_MAX];
263
264
6.50k
        if (!(avahi_unescape_label(&t, label, sizeof(label))))
265
112
            return 0;
266
267
6.38k
        if (strlen(label) <= 2 || label[0] != '_')
268
656
            return 0;
269
270
6.38k
    } while (*t);
271
272
1.88k
    return 1;
273
2.65k
}
274
275
9.09k
int avahi_is_valid_service_type_strict(const char *t) {
276
9.09k
    char label[AVAHI_LABEL_MAX];
277
9.09k
    assert(t);
278
279
9.09k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
280
1.69k
        return 0;
281
282
    /* Application name */
283
284
7.40k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
285
91
        return 0;
286
287
7.31k
    if (strlen(label) <= 2 || label[0] != '_')
288
972
        return 0;
289
290
6.34k
    if (!*t)
291
1.43k
        return 0;
292
293
    /* _tcp or _udp boilerplate */
294
295
4.90k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
296
10
        return 0;
297
298
4.89k
    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
299
3.06k
        return 0;
300
301
1.83k
    if (*t)
302
596
        return 0;
303
304
1.24k
    return 1;
305
1.83k
}
306
307
7.85k
const char *avahi_get_type_from_subtype(const char *t) {
308
7.85k
    char label[AVAHI_LABEL_MAX];
309
7.85k
    const char *ret;
310
7.85k
    assert(t);
311
312
7.85k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
313
1.69k
        return NULL;
314
315
    /* Subtype name */
316
317
6.16k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
318
91
        return NULL;
319
320
6.07k
    if (strlen(label) <= 2 || label[0] != '_')
321
972
        return NULL;
322
323
5.10k
    if (!*t)
324
1.43k
        return NULL;
325
326
    /* String "_sub" */
327
328
3.66k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
329
10
        return NULL;
330
331
3.65k
    if (strcasecmp(label, "_sub"))
332
1.70k
        return NULL;
333
334
1.95k
    if (!*t)
335
246
        return NULL;
336
337
1.71k
    ret = t;
338
339
    /* Application name */
340
341
1.71k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
342
2
        return NULL;
343
344
1.70k
    if (strlen(label) <= 2 || label[0] != '_')
345
232
        return NULL;
346
347
1.47k
    if (!*t)
348
229
        return NULL;
349
350
    /* _tcp or _udp boilerplate */
351
352
1.24k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
353
2
        return NULL;
354
355
1.24k
    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
356
314
        return NULL;
357
358
932
    if (*t)
359
394
        return NULL;
360
361
538
    return ret;
362
932
}
363
364
7.85k
int avahi_is_valid_service_subtype(const char *t) {
365
7.85k
    assert(t);
366
367
7.85k
    return !!avahi_get_type_from_subtype(t);
368
7.85k
}
369
370
151k
int avahi_is_valid_domain_name(const char *t) {
371
151k
    int is_first = 1;
372
151k
    assert(t);
373
374
151k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
375
42
        return 0;
376
377
337k
    do {
378
337k
        char label[AVAHI_LABEL_MAX];
379
380
337k
        if (!(avahi_unescape_label(&t, label, sizeof(label))))
381
466
            return 0;
382
383
        /* Explicitly allow the root domain name */
384
337k
        if (is_first && label[0] == 0 && *t == 0)
385
128k
            return 1;
386
387
208k
        is_first = 0;
388
389
208k
        if (label[0] == 0)
390
48
            return 0;
391
392
208k
    } while (*t);
393
394
22.7k
    return 1;
395
151k
}
396
397
7.56k
int avahi_is_valid_service_name(const char *t) {
398
7.56k
    assert(t);
399
400
7.56k
    if (strlen(t) >= AVAHI_LABEL_MAX || !*t || !avahi_utf8_valid(t))
401
985
        return 0;
402
403
6.57k
    return 1;
404
7.56k
}
405
406
2.23k
int avahi_is_valid_host_name(const char *t) {
407
2.23k
    char label[AVAHI_LABEL_MAX];
408
2.23k
    assert(t);
409
410
2.23k
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
411
90
        return 0;
412
413
2.14k
    if (!(avahi_unescape_label(&t, label, sizeof(label))))
414
182
        return 0;
415
416
1.96k
    if (strlen(label) < 1)
417
26
        return 0;
418
419
1.93k
    if (*t)
420
768
        return 0;
421
422
1.16k
    return 1;
423
1.93k
}
424
425
605
unsigned avahi_domain_hash(const char *s) {
426
605
    unsigned hash = 0;
427
428
10.5k
    while (*s) {
429
9.92k
        char c[AVAHI_LABEL_MAX], *p, *r;
430
431
9.92k
        r = avahi_unescape_label(&s, c, sizeof(c));
432
9.92k
        assert(r);
433
434
22.8k
        for (p = c; *p; p++)
435
12.9k
            hash = 31 * hash + tolower(*p);
436
9.92k
    }
437
438
605
    return hash;
439
605
}
440
441
1.77k
int avahi_service_name_join(char *p, size_t size, const char *name, const char *type, const char *domain) {
442
1.77k
    char escaped_name[AVAHI_LABEL_MAX*4];
443
1.77k
    char normalized_type[AVAHI_DOMAIN_NAME_MAX];
444
1.77k
    char normalized_domain[AVAHI_DOMAIN_NAME_MAX];
445
446
1.77k
    assert(p);
447
448
    /* Validity checks */
449
450
1.77k
    if ((name && !avahi_is_valid_service_name(name)))
451
0
        return AVAHI_ERR_INVALID_SERVICE_NAME;
452
453
1.77k
    if (!avahi_is_valid_service_type_generic(type))
454
0
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
455
456
1.77k
    if (!avahi_is_valid_domain_name(domain))
457
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
458
459
    /* Preparation */
460
461
1.77k
    if (name) {
462
688
        size_t l = sizeof(escaped_name);
463
688
        char *e = escaped_name, *r;
464
688
        r = avahi_escape_label(name, strlen(name), &e, &l);
465
688
        assert(r);
466
688
    }
467
468
1.77k
    if (!(avahi_normalize_name(type, normalized_type, sizeof(normalized_type))))
469
0
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
470
471
1.77k
    if (!(avahi_normalize_name(domain, normalized_domain, sizeof(normalized_domain))))
472
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
473
474
    /* Concatenation */
475
476
1.77k
    snprintf(p, size, "%s%s%s.%s", name ? escaped_name : "", name ? "." : "", normalized_type, normalized_domain);
477
478
1.77k
    return AVAHI_OK;
479
1.77k
}
480
481
#ifndef HAVE_STRLCPY
482
483
3.83k
static size_t strlcpy(char *dest, const char *src, size_t n) {
484
3.83k
    assert(dest);
485
3.83k
    assert(src);
486
487
3.83k
    if (n > 0) {
488
3.83k
        strncpy(dest, src, n-1);
489
3.83k
        dest[n-1] = 0;
490
3.83k
    }
491
492
3.83k
    return strlen(src);
493
3.83k
}
494
495
#endif
496
497
8.69k
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) {
498
8.69k
    enum {
499
8.69k
        NAME,
500
8.69k
        TYPE,
501
8.69k
        DOMAIN
502
8.69k
    } state;
503
8.69k
    int type_empty = 1, domain_empty = 1;
504
8.69k
    char *oname, *otype, *odomain;
505
506
8.69k
    assert(p);
507
8.69k
    assert(type);
508
8.69k
    assert(type_size > 0);
509
8.69k
    assert(domain);
510
8.69k
    assert(domain_size > 0);
511
512
8.69k
    oname = name;
513
8.69k
    otype = type;
514
8.69k
    odomain = domain;
515
516
8.69k
    if (name) {
517
4.34k
        assert(name_size > 0);
518
4.34k
        *name = 0;
519
4.34k
        state = NAME;
520
4.34k
    } else
521
4.34k
        state = TYPE;
522
523
8.69k
    *type = *domain = 0;
524
525
42.5k
    while (*p) {
526
33.8k
        char buf[64];
527
528
33.8k
        if (!(avahi_unescape_label(&p, buf, sizeof(buf))))
529
0
            return -1;
530
531
33.8k
        switch (state) {
532
3.83k
            case NAME:
533
3.83k
                strlcpy(name, buf, name_size);
534
3.83k
                state = TYPE;
535
3.83k
                break;
536
537
19.2k
            case TYPE:
538
539
19.2k
                if (buf[0] == '_') {
540
541
16.2k
                    if (!type_empty) {
542
9.71k
                        if (!type_size)
543
0
                            return AVAHI_ERR_NO_MEMORY;
544
545
9.71k
                        *(type++) = '.';
546
9.71k
                        type_size --;
547
548
9.71k
                    } else
549
6.53k
                        type_empty = 0;
550
551
16.2k
                    if (!(avahi_escape_label(buf, strlen(buf), &type, &type_size)))
552
0
                        return AVAHI_ERR_NO_MEMORY;
553
554
16.2k
                    break;
555
16.2k
                }
556
557
2.97k
                state = DOMAIN;
558
                /* fall through */
559
560
13.7k
            case DOMAIN:
561
562
13.7k
                if (!domain_empty) {
563
10.7k
                    if (!domain_size)
564
0
                        return AVAHI_ERR_NO_MEMORY;
565
566
10.7k
                    *(domain++) = '.';
567
10.7k
                    domain_size --;
568
10.7k
                } else
569
2.97k
                    domain_empty = 0;
570
571
13.7k
                if (!(avahi_escape_label(buf, strlen(buf), &domain, &domain_size)))
572
0
                    return AVAHI_ERR_NO_MEMORY;
573
574
13.7k
                break;
575
33.8k
        }
576
33.8k
    }
577
578
8.69k
    if ((oname && !avahi_is_valid_service_name(oname)))
579
513
        return AVAHI_ERR_INVALID_SERVICE_NAME;
580
581
8.17k
    if (!avahi_is_valid_service_type_strict(otype) && !avahi_is_valid_service_subtype(otype))
582
6.40k
        return AVAHI_ERR_INVALID_SERVICE_TYPE;
583
584
1.77k
    if (!avahi_is_valid_domain_name(odomain))
585
0
        return AVAHI_ERR_INVALID_DOMAIN_NAME;
586
587
1.77k
    return 0;
588
1.77k
}
589
590
922
int avahi_is_valid_fqdn(const char *t) {
591
922
    char label[AVAHI_LABEL_MAX];
592
922
    char normalized[AVAHI_DOMAIN_NAME_MAX];
593
922
    const char *k = t;
594
922
    AvahiAddress a;
595
922
    assert(t);
596
597
922
    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
598
42
        return 0;
599
600
880
    if (!avahi_is_valid_domain_name(t))
601
206
        return 0;
602
603
    /* Check if there are at least two labels*/
604
674
    if (!(avahi_unescape_label(&k, label, sizeof(label))))
605
0
        return 0;
606
607
674
    if (label[0] == 0 || !k)
608
4
        return 0;
609
610
670
    if (!(avahi_unescape_label(&k, label, sizeof(label))))
611
0
        return 0;
612
613
670
    if (label[0] == 0 || !k)
614
389
        return 0;
615
616
    /* Make sure that the name is not an IP address */
617
281
    if (!(avahi_normalize_name(t, normalized, sizeof(normalized))))
618
9
        return 0;
619
620
272
    if (avahi_address_parse(normalized, AVAHI_PROTO_UNSPEC, &a))
621
1
        return 0;
622
623
271
    return 1;
624
272
}