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