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