blob: 3b1ab683494e9e5caf6094944bd4e2e41ae96bdd [file] [log] [blame]
/***
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;
}