--- embedaddon/dnsmasq/src/util.c 2014/06/15 16:31:38 1.1.1.2 +++ embedaddon/dnsmasq/src/util.c 2023/09/27 11:02:07 1.1.1.5 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,10 +24,16 @@ #include #endif -#if defined(LOCALEDIR) || defined(HAVE_IDN) +#if defined(HAVE_LIBIDN2) +#include +#elif defined(HAVE_IDN) #include #endif +#ifdef HAVE_LINUX_NETWORK +#include +#endif + /* SURF random number generator */ static u32 seed[32]; @@ -81,6 +87,18 @@ unsigned short rand16(void) return (unsigned short) out[--outleft]; } +u32 rand32(void) +{ + if (!outleft) + { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + return out[--outleft]; +} + u64 rand64(void) { static int outleft = 0; @@ -97,6 +115,8 @@ u64 rand64(void) return (u64)out[outleft+1] + (((u64)out[outleft]) << 32); } +/* returns 1 if name is OK and ascii printable + * returns 2 if name should be processed by IDN */ static int check_name(char *in) { /* remove trailing . @@ -104,6 +124,9 @@ static int check_name(char *in) size_t dotgap = 0, l = strlen(in); char c; int nowhite = 0; + int idn_encode = 0; + int hasuscore = 0; + int hasucase = 0; if (l == 0 || l > MAXDNAME) return 0; @@ -116,24 +139,49 @@ static int check_name(char *in) for (; (c = *in); in++) { if (c == '.') - dotgap = 0; + dotgap = 0; else if (++dotgap > MAXLABEL) - return 0; + return 0; else if (isascii((unsigned char)c) && iscntrl((unsigned char)c)) - /* iscntrl only gives expected results for ascii */ - return 0; -#if !defined(LOCALEDIR) && !defined(HAVE_IDN) + /* iscntrl only gives expected results for ascii */ + return 0; else if (!isascii((unsigned char)c)) - return 0; +#if !defined(HAVE_IDN) && !defined(HAVE_LIBIDN2) + return 0; +#else + idn_encode = 1; #endif else if (c != ' ') - nowhite = 1; + { + nowhite = 1; +#if defined(HAVE_LIBIDN2) && (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003) + if (c == '_') + hasuscore = 1; +#else + (void)hasuscore; +#endif + +#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) + if (c >= 'A' && c <= 'Z') + hasucase = 1; +#else + (void)hasucase; +#endif + } } if (!nowhite) return 0; - return 1; +#if defined(HAVE_LIBIDN2) && (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003) + /* Older libidn2 strips underscores, so don't do IDN processing + if the name has an underscore unless it also has non-ascii characters. */ + idn_encode = idn_encode || (hasucase && !hasuscore); +#else + idn_encode = idn_encode || hasucase; +#endif + + return (idn_encode) ? 2 : 1; } /* Hostnames have a more limited valid charset than domain names @@ -172,67 +220,104 @@ int legal_hostname(char *name) char *canonicalise(char *in, int *nomem) { char *ret = NULL; -#if defined(LOCALEDIR) || defined(HAVE_IDN) int rc; -#endif - + if (nomem) *nomem = 0; - if (!check_name(in)) + if (!(rc = check_name(in))) return NULL; -#if defined(LOCALEDIR) || defined(HAVE_IDN) - if ((rc = idna_to_ascii_lz(in, &ret, 0)) != IDNA_SUCCESS) +#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) + if (rc == 2) { - if (ret) - free(ret); - - if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR)) +# ifdef HAVE_LIBIDN2 + rc = idn2_to_ascii_lz(in, &ret, IDN2_NONTRANSITIONAL); +# else + rc = idna_to_ascii_lz(in, &ret, 0); +# endif + if (rc != IDNA_SUCCESS) { - my_syslog(LOG_ERR, _("failed to allocate memory")); - *nomem = 1; + if (ret) + free(ret); + + if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR)) + { + my_syslog(LOG_ERR, _("failed to allocate memory")); + *nomem = 1; + } + + return NULL; } - - return NULL; + + return ret; } #else + (void)rc; +#endif + if ((ret = whine_malloc(strlen(in)+1))) strcpy(ret, in); else if (nomem) - *nomem = 1; -#endif + *nomem = 1; return ret; } -unsigned char *do_rfc1035_name(unsigned char *p, char *sval) +unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit) { int j; while (sval && *sval) { unsigned char *cp = p++; + + if (limit && p > (unsigned char*)limit) + return NULL; + for (j = 0; *sval && (*sval != '.'); sval++, j++) - *p++ = *sval; + { + if (limit && p + 1 > (unsigned char*)limit) + return NULL; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE) + *p++ = (*(++sval))-1; + else +#endif + *p++ = *sval; + } + *cp = j; if (*sval) sval++; } + return p; } /* for use during startup */ void *safe_malloc(size_t size) { - void *ret = malloc(size); + void *ret = calloc(1, size); if (!ret) die(_("could not get memory"), NULL, EC_NOMEM); - + return ret; -} +} +/* Ensure limited size string is always terminated. + * Can be replaced by (void)strlcpy() on some platforms */ +void safe_strncpy(char *dest, const char *src, size_t size) +{ + if (size != 0) + { + dest[size-1] = '\0'; + strncpy(dest, src, size-1); + } +} + void safe_pipe(int *fd, int read_noblock) { if (pipe(fd) == -1 || @@ -243,15 +328,25 @@ void safe_pipe(int *fd, int read_noblock) void *whine_malloc(size_t size) { - void *ret = malloc(size); + void *ret = calloc(1, size); if (!ret) my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size); + + return ret; +} +void *whine_realloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + if (!ret) + my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size); + return ret; } -int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) +int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) { if (s1->sa.sa_family == s2->sa.sa_family) { @@ -259,32 +354,43 @@ int sockaddr_isequal(union mysockaddr *s1, union mysoc s1->in.sin_port == s2->in.sin_port && s1->in.sin_addr.s_addr == s2->in.sin_addr.s_addr) return 1; -#ifdef HAVE_IPV6 + if (s1->sa.sa_family == AF_INET6 && s1->in6.sin6_port == s2->in6.sin6_port && + s1->in6.sin6_scope_id == s2->in6.sin6_scope_id && IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr)) return 1; -#endif } return 0; } +int sockaddr_isnull(const union mysockaddr *s) +{ + if (s->sa.sa_family == AF_INET && + s->in.sin_addr.s_addr == 0) + return 1; + + if (s->sa.sa_family == AF_INET6 && + IN6_IS_ADDR_UNSPECIFIED(&s->in6.sin6_addr)) + return 1; + + return 0; +} + int sa_len(union mysockaddr *addr) { #ifdef HAVE_SOCKADDR_SA_LEN return addr->sa.sa_len; #else -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6) return sizeof(addr->in6); else -#endif return sizeof(addr->in); #endif } /* don't use strcasecmp and friends here - they may be messed up by LOCALE */ -int hostname_isequal(const char *a, const char *b) +int hostname_order(const char *a, const char *b) { unsigned int c1, c2; @@ -297,34 +403,110 @@ int hostname_isequal(const char *a, const char *b) if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; - if (c1 != c2) - return 0; + if (c1 < c2) + return -1; + else if (c1 > c2) + return 1; + } while (c1); - return 1; + return 0; } - + +int hostname_isequal(const char *a, const char *b) +{ + return hostname_order(a, b) == 0; +} + +/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */ +int hostname_issubdomain(char *a, char *b) +{ + char *ap, *bp; + unsigned int c1, c2; + + /* move to the end */ + for (ap = a; *ap; ap++); + for (bp = b; *bp; bp++); + + /* a shorter than b or a empty. */ + if ((bp - b) < (ap - a) || ap == a) + return 0; + + do + { + c1 = (unsigned char) *(--ap); + c2 = (unsigned char) *(--bp); + + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + return 0; + } while (ap != a); + + if (bp == b) + return 2; + + if (*(--bp) == '.') + return 1; + + return 0; +} + + time_t dnsmasq_time(void) { #ifdef HAVE_BROKEN_RTC - struct tms dummy; - static long tps = 0; + struct timespec ts; - if (tps == 0) - tps = sysconf(_SC_CLK_TCK); + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + die(_("cannot read monotonic clock: %s"), NULL, EC_MISC); - return (time_t)(times(&dummy)/tps); + return ts.tv_sec; #else return time(NULL); #endif } +u32 dnsmasq_milliseconds(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (tv.tv_sec) * 1000 + (tv.tv_usec / 1000); +} + +int netmask_length(struct in_addr mask) +{ + int zero_count = 0; + + while (0x0 == (mask.s_addr & 0x1) && zero_count < 32) + { + mask.s_addr >>= 1; + zero_count++; + } + + return 32 - zero_count; +} + int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) { return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); -} +} -#ifdef HAVE_IPV6 +int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix) +{ + struct in_addr mask; + + mask.s_addr = htonl(~((1 << (32 - prefix)) - 1)); + + return is_same_net(a, b, mask); +} + + int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen) { int pfbytes = prefixlen >> 3; @@ -340,7 +522,7 @@ int is_same_net6(struct in6_addr *a, struct in6_addr * return 0; } -/* return least signigicant 64 bits if IPv6 address */ +/* return least significant 64 bits if IPv6 address */ u64 addr6part(struct in6_addr *addr) { int i; @@ -363,15 +545,12 @@ void setaddr6part(struct in6_addr *addr, u64 host) } } -#endif - /* returns port number from address */ int prettyprint_addr(union mysockaddr *addr, char *buf) { int port = 0; -#ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET) { inet_ntop(AF_INET, &addr->in.sin_addr, buf, ADDRSTRLEN); @@ -390,10 +569,6 @@ int prettyprint_addr(union mysockaddr *addr, char *buf } port = ntohs(addr->in6.sin6_port); } -#else - strcpy(buf, inet_ntoa(addr->in.sin_addr)); - port = ntohs(addr->in.sin_port); -#endif return port; } @@ -406,13 +581,13 @@ void prettyprint_time(char *buf, unsigned int t) { unsigned int x, p = 0; if ((x = t/86400)) - p += sprintf(&buf[p], "%dd", x); + p += sprintf(&buf[p], "%ud", x); if ((x = (t/3600)%24)) - p += sprintf(&buf[p], "%dh", x); + p += sprintf(&buf[p], "%uh", x); if ((x = (t/60)%60)) - p += sprintf(&buf[p], "%dm", x); + p += sprintf(&buf[p], "%um", x); if ((x = t%60)) - p += sprintf(&buf[p], "%ds", x); + sprintf(&buf[p], "%us", x); } } @@ -422,20 +597,20 @@ void prettyprint_time(char *buf, unsigned int t) int parse_hex(char *in, unsigned char *out, int maxlen, unsigned int *wildcard_mask, int *mac_type) { - int mask = 0, i = 0; + int done = 0, mask = 0, i = 0; char *r; if (mac_type) *mac_type = 0; - while (maxlen == -1 || i < maxlen) + while (!done && (maxlen == -1 || i < maxlen)) { for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++) if (*r != '*' && !isxdigit((unsigned char)*r)) return -1; if (*r == 0) - maxlen = i; + done = 1; if (r != in ) { @@ -458,15 +633,20 @@ int parse_hex(char *in, unsigned char *out, int maxlen int j, bytes = (1 + (r - in))/2; for (j = 0; j < bytes; j++) { - char sav = sav; + char sav; if (j < bytes - 1) { sav = in[(j+1)*2]; in[(j+1)*2] = 0; } + /* checks above allow mix of hexdigit and *, which + is illegal. */ + if (strchr(&in[j*2], '*')) + return -1; out[i] = strtol(&in[j*2], NULL, 16); mask = mask << 1; - i++; + if (++i == maxlen) + break; if (j < bytes - 1) in[(j+1)*2] = sav; } @@ -537,27 +717,41 @@ char *print_mac(char *buff, unsigned char *mac, int le return buff; } -void bump_maxfd(int fd, int *max) +/* rc is return from sendto and friends. + Return 1 if we should retry. + Set errno to zero if we succeeded. */ +int retry_send(ssize_t rc) { - if (fd > *max) - *max = fd; -} + static int retries = 0; + struct timespec waiter; + + if (rc != -1) + { + retries = 0; + errno = 0; + return 0; + } + + /* Linux kernels can return EAGAIN in perpetuity when calling + sendmsg() and the relevant interface has gone. Here we loop + retrying in EAGAIN for 1 second max, to avoid this hanging + dnsmasq. */ -int retry_send(void) -{ - struct timespec waiter; - if (errno == EAGAIN || errno == EWOULDBLOCK) + if (errno == EAGAIN || errno == EWOULDBLOCK) { waiter.tv_sec = 0; waiter.tv_nsec = 10000; nanosleep(&waiter, NULL); - return 1; + if (retries++ < 1000) + return 1; } - - if (errno == EINTR) - return 1; - - return 0; + + retries = 0; + + if (errno == EINTR) + return 1; + + return 0; } int read_write(int fd, unsigned char *packet, int size, int rw) @@ -566,25 +760,65 @@ int read_write(int fd, unsigned char *packet, int size for (done = 0; done < size; done += n) { - retry: - if (rw) - n = read(fd, &packet[done], (size_t)(size - done)); - else - n = write(fd, &packet[done], (size_t)(size - done)); + do { + if (rw) + n = read(fd, &packet[done], (size_t)(size - done)); + else + n = write(fd, &packet[done], (size_t)(size - done)); + + if (n == 0) + return 0; + + } while (retry_send(n) || errno == ENOMEM || errno == ENOBUFS); - if (n == 0) - return 0; - else if (n == -1) - { - if (retry_send() || errno == ENOMEM || errno == ENOBUFS) - goto retry; - else - return 0; - } + if (errno != 0) + return 0; } + return 1; } +/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */ +void close_fds(long max_fd, int spare1, int spare2, int spare3) +{ + /* On Linux, use the /proc/ filesystem to find which files + are actually open, rather than iterate over the whole space, + for efficiency reasons. If this fails we drop back to the dumb code. */ +#ifdef HAVE_LINUX_NETWORK + DIR *d; + + if ((d = opendir("/proc/self/fd"))) + { + struct dirent *de; + + while ((de = readdir(d))) + { + long fd; + char *e = NULL; + + errno = 0; + fd = strtol(de->d_name, &e, 10); + + if (errno != 0 || !e || *e || fd == dirfd(d) || + fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO || + fd == spare1 || fd == spare2 || fd == spare3) + continue; + + close(fd); + } + + closedir(d); + return; + } +#endif + + /* fallback, dumb code. */ + for (max_fd--; max_fd >= 0; max_fd--) + if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO && + max_fd != spare1 && max_fd != spare2 && max_fd != spare3) + close(max_fd); +} + /* Basically match a string value against a wildcard pattern. */ int wildcard_match(const char* wildcard, const char* match) { @@ -602,3 +836,41 @@ int wildcard_match(const char* wildcard, const char* m return *wildcard == *match; } + +/* The same but comparing a maximum of NUM characters, like strncmp. */ +int wildcard_matchn(const char* wildcard, const char* match, int num) +{ + while (*wildcard && *match && num) + { + if (*wildcard == '*') + return 1; + + if (*wildcard != *match) + return 0; + + ++wildcard; + ++match; + --num; + } + + return (!num) || (*wildcard == *match); +} + +#ifdef HAVE_LINUX_NETWORK +int kernel_version(void) +{ + struct utsname utsname; + int version; + char *split; + + if (uname(&utsname) < 0) + die(_("failed to find kernel version: %s"), NULL, EC_MISC); + + split = strtok(utsname.release, "."); + version = (split ? atoi(split) : 0); + split = strtok(NULL, "."); + version = version * 256 + (split ? atoi(split) : 0); + split = strtok(NULL, "."); + return version * 256 + (split ? atoi(split) : 0); +} +#endif