| /*** |
| This file is part of avahi. |
| |
| avahi is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as |
| published by the Free Software Foundation; either version 2.1 of the |
| License, or (at your option) any later version. |
| |
| avahi is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General |
| Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with avahi; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| USA. |
| ***/ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <assert.h> |
| |
| #include "domain.h" |
| #include "malloc.h" |
| #include "error.h" |
| #include "address.h" |
| #include "utf8.h" |
| |
| /* Read the first label from string *name, unescape "\" and write it to dest */ |
| char *avahi_unescape_label(const char **name, char *dest, size_t size) { |
| unsigned i = 0; |
| char *d; |
| |
| assert(dest); |
| assert(size > 0); |
| assert(name); |
| |
| d = dest; |
| |
| for (;;) { |
| if (i >= size) |
| return NULL; |
| |
| if (**name == '.') { |
| (*name)++; |
| break; |
| } |
| |
| if (**name == 0) |
| break; |
| |
| if (**name == '\\') { |
| /* Escaped character */ |
| |
| (*name) ++; |
| |
| if (**name == 0) |
| /* Ending NUL */ |
| return NULL; |
| |
| else if (**name == '\\' || **name == '.') { |
| /* Escaped backslash or dot */ |
| *(d++) = *((*name) ++); |
| i++; |
| } else if (isdigit(**name)) { |
| int n; |
| |
| /* Escaped literal ASCII character */ |
| |
| if (!isdigit(*(*name+1)) || !isdigit(*(*name+2))) |
| return NULL; |
| |
| n = ((uint8_t) (**name - '0') * 100) + ((uint8_t) (*(*name+1) - '0') * 10) + ((uint8_t) (*(*name +2) - '0')); |
| |
| if (n > 255 || n == 0) |
| return NULL; |
| |
| *(d++) = (char) n; |
| i++; |
| |
| (*name) += 3; |
| } else |
| return NULL; |
| |
| } else { |
| |
| /* Normal character */ |
| |
| *(d++) = *((*name) ++); |
| i++; |
| } |
| } |
| |
| assert(i < size); |
| |
| *d = 0; |
| |
| if (!avahi_utf8_valid(dest)) |
| return NULL; |
| |
| return dest; |
| } |
| |
| /* Escape "\" and ".", append \0 */ |
| char *avahi_escape_label(const char* src, size_t src_length, char **ret_name, size_t *ret_size) { |
| char *r; |
| |
| assert(src); |
| assert(ret_name); |
| assert(*ret_name); |
| assert(ret_size); |
| assert(*ret_size > 0); |
| |
| r = *ret_name; |
| |
| while (src_length > 0) { |
| if (*src == '.' || *src == '\\') { |
| |
| /* Dot or backslash */ |
| |
| if (*ret_size < 3) |
| return NULL; |
| |
| *((*ret_name) ++) = '\\'; |
| *((*ret_name) ++) = *src; |
| (*ret_size) -= 2; |
| |
| } else if ( |
| *src == '_' || |
| *src == '-' || |
| (*src >= '0' && *src <= '9') || |
| (*src >= 'a' && *src <= 'z') || |
| (*src >= 'A' && *src <= 'Z')) { |
| |
| /* Proper character */ |
| |
| if (*ret_size < 2) |
| return NULL; |
| |
| *((*ret_name)++) = *src; |
| (*ret_size) --; |
| |
| } else { |
| |
| /* Everything else */ |
| |
| if (*ret_size < 5) |
| return NULL; |
| |
| *((*ret_name) ++) = '\\'; |
| *((*ret_name) ++) = '0' + (char) ((uint8_t) *src / 100); |
| *((*ret_name) ++) = '0' + (char) (((uint8_t) *src / 10) % 10); |
| *((*ret_name) ++) = '0' + (char) ((uint8_t) *src % 10); |
| |
| (*ret_size) -= 4; |
| } |
| |
| src_length --; |
| src++; |
| } |
| |
| **ret_name = 0; |
| |
| return r; |
| } |
| |
| char *avahi_normalize_name(const char *s, char *ret_s, size_t size) { |
| int empty = 1; |
| char *r; |
| |
| assert(s); |
| assert(ret_s); |
| assert(size > 0); |
| |
| r = ret_s; |
| *ret_s = 0; |
| |
| while (*s) { |
| char label[AVAHI_LABEL_MAX]; |
| |
| if (!(avahi_unescape_label(&s, label, sizeof(label)))) |
| return NULL; |
| |
| if (label[0] == 0) { |
| |
| if (*s == 0 && empty) |
| return ret_s; |
| |
| return NULL; |
| } |
| |
| if (!empty) { |
| if (size < 1) |
| return NULL; |
| |
| *(r++) = '.'; |
| size--; |
| |
| } else |
| empty = 0; |
| |
| avahi_escape_label(label, strlen(label), &r, &size); |
| } |
| |
| return ret_s; |
| } |
| |
| char *avahi_normalize_name_strdup(const char *s) { |
| char t[AVAHI_DOMAIN_NAME_MAX]; |
| assert(s); |
| |
| if (!(avahi_normalize_name(s, t, sizeof(t)))) |
| return NULL; |
| |
| return avahi_strdup(t); |
| } |
| |
| int avahi_domain_equal(const char *a, const char *b) { |
| assert(a); |
| assert(b); |
| |
| if (a == b) |
| return 1; |
| |
| for (;;) { |
| char ca[AVAHI_LABEL_MAX], cb[AVAHI_LABEL_MAX], *r; |
| |
| r = avahi_unescape_label(&a, ca, sizeof(ca)); |
| assert(r); |
| r = avahi_unescape_label(&b, cb, sizeof(cb)); |
| assert(r); |
| |
| if (strcasecmp(ca, cb)) |
| return 0; |
| |
| if (!*a && !*b) |
| return 1; |
| } |
| |
| return 1; |
| } |
| |
| int avahi_is_valid_service_type_generic(const char *t) { |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t) |
| return 0; |
| |
| do { |
| char label[AVAHI_LABEL_MAX]; |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return 0; |
| |
| if (strlen(label) <= 2 || label[0] != '_') |
| return 0; |
| |
| } while (*t); |
| |
| return 1; |
| } |
| |
| int avahi_is_valid_service_type_strict(const char *t) { |
| char label[AVAHI_LABEL_MAX]; |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t) |
| return 0; |
| |
| /* Application name */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return 0; |
| |
| if (strlen(label) <= 2 || label[0] != '_') |
| return 0; |
| |
| if (!*t) |
| return 0; |
| |
| /* _tcp or _udp boilerplate */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return 0; |
| |
| if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp")) |
| return 0; |
| |
| if (*t) |
| return 0; |
| |
| return 1; |
| } |
| |
| const char *avahi_get_type_from_subtype(const char *t) { |
| char label[AVAHI_LABEL_MAX]; |
| const char *ret; |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t) |
| return NULL; |
| |
| /* Subtype name */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return NULL; |
| |
| if (strlen(label) <= 2 || label[0] != '_') |
| return NULL; |
| |
| if (!*t) |
| return NULL; |
| |
| /* String "_sub" */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return NULL; |
| |
| if (strcasecmp(label, "_sub")) |
| return NULL; |
| |
| if (!*t) |
| return NULL; |
| |
| ret = t; |
| |
| /* Application name */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return NULL; |
| |
| if (strlen(label) <= 2 || label[0] != '_') |
| return NULL; |
| |
| if (!*t) |
| return NULL; |
| |
| /* _tcp or _udp boilerplate */ |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return NULL; |
| |
| if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp")) |
| return NULL; |
| |
| if (*t) |
| return NULL; |
| |
| return ret; |
| } |
| |
| int avahi_is_valid_service_subtype(const char *t) { |
| assert(t); |
| |
| return !!avahi_get_type_from_subtype(t); |
| } |
| |
| int avahi_is_valid_domain_name(const char *t) { |
| int is_first = 1; |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX) |
| return 0; |
| |
| do { |
| char label[AVAHI_LABEL_MAX]; |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return 0; |
| |
| /* Explicitly allow the root domain name */ |
| if (is_first && label[0] == 0 && *t == 0) |
| return 1; |
| |
| is_first = 0; |
| |
| if (label[0] == 0) |
| return 0; |
| |
| } while (*t); |
| |
| return 1; |
| } |
| |
| int avahi_is_valid_service_name(const char *t) { |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_LABEL_MAX || !*t) |
| return 0; |
| |
| return 1; |
| } |
| |
| int avahi_is_valid_host_name(const char *t) { |
| char label[AVAHI_LABEL_MAX]; |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t) |
| return 0; |
| |
| if (!(avahi_unescape_label(&t, label, sizeof(label)))) |
| return 0; |
| |
| if (strlen(label) < 1) |
| return 0; |
| |
| if (*t) |
| return 0; |
| |
| return 1; |
| } |
| |
| unsigned avahi_domain_hash(const char *s) { |
| unsigned hash = 0; |
| |
| while (*s) { |
| char c[AVAHI_LABEL_MAX], *p, *r; |
| |
| r = avahi_unescape_label(&s, c, sizeof(c)); |
| assert(r); |
| |
| for (p = c; *p; p++) |
| hash = 31 * hash + tolower(*p); |
| } |
| |
| return hash; |
| } |
| |
| int avahi_service_name_join(char *p, size_t size, const char *name, const char *type, const char *domain) { |
| char escaped_name[AVAHI_LABEL_MAX*4]; |
| char normalized_type[AVAHI_DOMAIN_NAME_MAX]; |
| char normalized_domain[AVAHI_DOMAIN_NAME_MAX]; |
| |
| assert(p); |
| |
| /* Validity checks */ |
| |
| if ((name && !avahi_is_valid_service_name(name))) |
| return AVAHI_ERR_INVALID_SERVICE_NAME; |
| |
| if (!avahi_is_valid_service_type_generic(type)) |
| return AVAHI_ERR_INVALID_SERVICE_TYPE; |
| |
| if (!avahi_is_valid_domain_name(domain)) |
| return AVAHI_ERR_INVALID_DOMAIN_NAME; |
| |
| /* Preparation */ |
| |
| if (name) { |
| size_t l = sizeof(escaped_name); |
| char *e = escaped_name, *r; |
| r = avahi_escape_label(name, strlen(name), &e, &l); |
| assert(r); |
| } |
| |
| if (!(avahi_normalize_name(type, normalized_type, sizeof(normalized_type)))) |
| return AVAHI_ERR_INVALID_SERVICE_TYPE; |
| |
| if (!(avahi_normalize_name(domain, normalized_domain, sizeof(normalized_domain)))) |
| return AVAHI_ERR_INVALID_DOMAIN_NAME; |
| |
| /* Concatenation */ |
| |
| snprintf(p, size, "%s%s%s.%s", name ? escaped_name : "", name ? "." : "", normalized_type, normalized_domain); |
| |
| return AVAHI_OK; |
| } |
| |
| #ifndef HAVE_STRLCPY |
| |
| static size_t strlcpy(char *dest, const char *src, size_t n) { |
| assert(dest); |
| assert(src); |
| |
| if (n > 0) { |
| strncpy(dest, src, n-1); |
| dest[n-1] = 0; |
| } |
| |
| return strlen(src); |
| } |
| |
| #endif |
| |
| 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) { |
| enum { |
| NAME, |
| TYPE, |
| DOMAIN |
| } state; |
| int type_empty = 1, domain_empty = 1; |
| |
| assert(p); |
| assert(type); |
| assert(type_size > 0); |
| assert(domain); |
| assert(domain_size > 0); |
| |
| if (name) { |
| assert(name_size > 0); |
| *name = 0; |
| state = NAME; |
| } else |
| state = TYPE; |
| |
| *type = *domain = 0; |
| |
| while (*p) { |
| char buf[64]; |
| |
| if (!(avahi_unescape_label(&p, buf, sizeof(buf)))) |
| return -1; |
| |
| switch (state) { |
| case NAME: |
| strlcpy(name, buf, name_size); |
| state = TYPE; |
| break; |
| |
| case TYPE: |
| |
| if (buf[0] == '_') { |
| |
| if (!type_empty) { |
| if (!type_size) |
| return AVAHI_ERR_NO_MEMORY; |
| |
| *(type++) = '.'; |
| type_size --; |
| |
| } else |
| type_empty = 0; |
| |
| if (!(avahi_escape_label(buf, strlen(buf), &type, &type_size))) |
| return AVAHI_ERR_NO_MEMORY; |
| |
| break; |
| } |
| |
| state = DOMAIN; |
| /* fall through */ |
| |
| case DOMAIN: |
| |
| if (!domain_empty) { |
| if (!domain_size) |
| return AVAHI_ERR_NO_MEMORY; |
| |
| *(domain++) = '.'; |
| domain_size --; |
| } else |
| domain_empty = 0; |
| |
| if (!(avahi_escape_label(buf, strlen(buf), &domain, &domain_size))) |
| return AVAHI_ERR_NO_MEMORY; |
| |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int avahi_is_valid_fqdn(const char *t) { |
| char label[AVAHI_LABEL_MAX]; |
| char normalized[AVAHI_DOMAIN_NAME_MAX]; |
| const char *k = t; |
| AvahiAddress a; |
| assert(t); |
| |
| if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX) |
| return 0; |
| |
| if (!avahi_is_valid_domain_name(t)) |
| return 0; |
| |
| /* Check if there are at least two labels*/ |
| if (!(avahi_unescape_label(&k, label, sizeof(label)))) |
| return 0; |
| |
| if (label[0] == 0 || !k) |
| return 0; |
| |
| if (!(avahi_unescape_label(&k, label, sizeof(label)))) |
| return 0; |
| |
| if (label[0] == 0 || !k) |
| return 0; |
| |
| /* Make sure that the name is not an IP address */ |
| if (!(avahi_normalize_name(t, normalized, sizeof(normalized)))) |
| return 0; |
| |
| if (avahi_address_parse(normalized, AVAHI_PROTO_UNSPEC, &a)) |
| return 0; |
| |
| return 1; |
| } |