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