mirror of
https://github.com/opnsense/src.git
synced 2026-04-21 06:07:31 -04:00
instead of accepting any character as a delimiter, only accept ':', '.' and '-', and only permit a single delimiter in an address. this prevents accepting bizarre addresses like: ifconfig epair2a link 10.1.2.200/28 ... which is particularly problematic on an INET6-only system, in which case ifconfig defaults to the 'link' family, meaning that: ifconfig epair2a 10.1.2.200/28 ... changes the Ethernet address of the interface. bump __FreeBSD_version so link_addr() consumers can detect the change. Reviewed by: kp, des Approved by: des (mentor) Differential Revision: https://reviews.freebsd.org/D49936 (cherry picked from commit a1215090416b8afb346fb2ff5b38f25ba0134a3a) Note-from-OPNsense: not bumping the FreeBSD version for stable/25.7
294 lines
7.6 KiB
C
294 lines
7.6 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 1990, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#if defined(LIBC_SCCS) && !defined(lint)
|
|
static char sccsid[] = "@(#)linkaddr.c 8.1 (Berkeley) 6/4/93";
|
|
#endif /* LIBC_SCCS and not lint */
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
int
|
|
link_addr(const char *addr, struct sockaddr_dl *sdl)
|
|
{
|
|
char *cp = sdl->sdl_data;
|
|
char *cplim = sdl->sdl_len + (char *)sdl;
|
|
const char *nptr;
|
|
size_t newsize;
|
|
int error = 0;
|
|
char delim = 0;
|
|
|
|
/* Initialise the sdl to zero, except for sdl_len. */
|
|
bzero((char *)&sdl->sdl_family, sdl->sdl_len - 1);
|
|
sdl->sdl_family = AF_LINK;
|
|
|
|
/*
|
|
* Everything up to the first ':' is the interface name. Usually the
|
|
* ':' should always be present even if there's no interface name, but
|
|
* since this interface was poorly specified in the past, accept a
|
|
* missing colon as meaning no interface name.
|
|
*/
|
|
if ((nptr = strchr(addr, ':')) != NULL) {
|
|
size_t namelen = nptr - addr;
|
|
|
|
/* Ensure the sdl is large enough to store the name. */
|
|
if (namelen > cplim - cp) {
|
|
errno = ENOSPC;
|
|
return (-1);
|
|
}
|
|
|
|
memcpy(cp, addr, namelen);
|
|
cp += namelen;
|
|
sdl->sdl_nlen = namelen;
|
|
/* Skip the interface name and the colon. */
|
|
addr += namelen + 1;
|
|
}
|
|
|
|
/*
|
|
* The remainder of the string should be hex digits representing the
|
|
* address, with optional delimiters. Each two hex digits form one
|
|
* octet, but octet output can be forced using a delimiter, so we accept
|
|
* a long string of hex digits, or a mix of delimited and undelimited
|
|
* digits like "1122.3344.5566", or delimited one- or two-digit octets
|
|
* like "1.22.3".
|
|
*
|
|
* If anything fails at this point, exit the loop so we set sdl_alen and
|
|
* sdl_len based on whatever we did manage to parse. This preserves
|
|
* compatibility with the 4.3BSD version of link_addr, which had no way
|
|
* to indicate an error and would just return.
|
|
*/
|
|
#define DIGIT(c) \
|
|
(((c) >= '0' && (c) <= '9') ? ((c) - '0') \
|
|
: ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) \
|
|
: ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) \
|
|
: (-1))
|
|
#define ISDELIM(c) (((c) == '.' || (c) == ':' || (c) == '-') && \
|
|
(delim == 0 || delim == (c)))
|
|
|
|
for (;;) {
|
|
int digit, digit2;
|
|
|
|
/*
|
|
* Treat any leading delimiters as empty bytes. This supports
|
|
* the (somewhat obsolete) form of Ethernet addresses with empty
|
|
* octets, e.g. "1::3:4:5:6".
|
|
*/
|
|
while (ISDELIM(*addr) && cp < cplim) {
|
|
delim = *addr++;
|
|
*cp++ = 0;
|
|
}
|
|
|
|
/* Did we reach the end of the string? */
|
|
if (*addr == '\0')
|
|
break;
|
|
|
|
/*
|
|
* If not, the next character must be a digit, so make sure we
|
|
* have room for at least one more octet.
|
|
*/
|
|
|
|
if (cp >= cplim) {
|
|
error = ENOSPC;
|
|
break;
|
|
}
|
|
|
|
if ((digit = DIGIT(*addr)) == -1) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
++addr;
|
|
|
|
/* If the next character is another digit, consume it. */
|
|
if ((digit2 = DIGIT(*addr)) != -1) {
|
|
digit = (digit << 4) | digit2;
|
|
++addr;
|
|
}
|
|
|
|
if (ISDELIM(*addr)) {
|
|
/*
|
|
* If the digit is followed by a delimiter, write it
|
|
* and consume the delimiter.
|
|
*/
|
|
delim = *addr++;
|
|
*cp++ = digit;
|
|
} else if (DIGIT(*addr) != -1) {
|
|
/*
|
|
* If two digits are followed by a third digit, treat
|
|
* the two digits we have as a single octet and
|
|
* continue.
|
|
*/
|
|
*cp++ = digit;
|
|
} else if (*addr == '\0') {
|
|
/* If the digit is followed by EOS, we're done. */
|
|
*cp++ = digit;
|
|
break;
|
|
} else {
|
|
/* Otherwise, the input was invalid. */
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
#undef DIGIT
|
|
#undef ISDELIM
|
|
|
|
/* How many bytes did we write to the address? */
|
|
sdl->sdl_alen = cp - LLADDR(sdl);
|
|
|
|
/*
|
|
* The user might have given us an sdl which is larger than sizeof(sdl);
|
|
* in that case, record the actual size of the new sdl.
|
|
*/
|
|
newsize = cp - (char *)sdl;
|
|
if (newsize > sizeof(*sdl))
|
|
sdl->sdl_len = (u_char)newsize;
|
|
|
|
if (error == 0)
|
|
return (0);
|
|
|
|
errno = error;
|
|
return (-1);
|
|
}
|
|
|
|
|
|
char *
|
|
link_ntoa(const struct sockaddr_dl *sdl)
|
|
{
|
|
static char obuf[64];
|
|
size_t buflen;
|
|
_Static_assert(sizeof(obuf) >= IFNAMSIZ + 20, "obuf is too small");
|
|
|
|
/*
|
|
* Ignoring the return value of link_ntoa_r() is safe here because it
|
|
* always writes the terminating NUL. This preserves the traditional
|
|
* behaviour of link_ntoa().
|
|
*/
|
|
buflen = sizeof(obuf);
|
|
(void)link_ntoa_r(sdl, obuf, &buflen);
|
|
return obuf;
|
|
}
|
|
|
|
int
|
|
link_ntoa_r(const struct sockaddr_dl *sdl, char *obuf, size_t *buflen)
|
|
{
|
|
static const char hexlist[] = "0123456789abcdef";
|
|
char *out;
|
|
const u_char *in, *inlim;
|
|
int namelen, i, rem;
|
|
size_t needed;
|
|
|
|
assert(sdl);
|
|
assert(buflen);
|
|
/* obuf may be null */
|
|
|
|
needed = 1; /* 1 for the NUL */
|
|
out = obuf;
|
|
if (obuf)
|
|
rem = *buflen;
|
|
else
|
|
rem = 0;
|
|
|
|
/*
|
|
* Check if at least n bytes are available in the output buffer, plus 1 for the
|
|
* trailing NUL. If not, set rem = 0 so we stop writing.
|
|
* Either way, increment needed by the amount we would have written.
|
|
*/
|
|
#define CHECK(n) do { \
|
|
if ((SIZE_MAX - (n)) >= needed) \
|
|
needed += (n); \
|
|
if (rem >= ((n) + 1)) \
|
|
rem -= (n); \
|
|
else \
|
|
rem = 0; \
|
|
} while (0)
|
|
|
|
/*
|
|
* Write the char c to the output buffer, unless the buffer is full.
|
|
* Note that if obuf is NULL, rem is always zero.
|
|
*/
|
|
#define OUT(c) do { \
|
|
if (rem > 0) \
|
|
*out++ = (c); \
|
|
} while (0)
|
|
|
|
namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
|
|
if (namelen > 0) {
|
|
CHECK(namelen);
|
|
if (rem > 0) {
|
|
bcopy(sdl->sdl_data, out, namelen);
|
|
out += namelen;
|
|
}
|
|
|
|
if (sdl->sdl_alen > 0) {
|
|
CHECK(1);
|
|
OUT(':');
|
|
}
|
|
}
|
|
|
|
in = (const u_char *)LLADDR(sdl);
|
|
inlim = in + sdl->sdl_alen;
|
|
|
|
while (in < inlim) {
|
|
if (in != (const u_char *)LLADDR(sdl)) {
|
|
CHECK(1);
|
|
OUT('.');
|
|
}
|
|
i = *in++;
|
|
if (i > 0xf) {
|
|
CHECK(2);
|
|
OUT(hexlist[i >> 4]);
|
|
OUT(hexlist[i & 0xf]);
|
|
} else {
|
|
CHECK(1);
|
|
OUT(hexlist[i]);
|
|
}
|
|
}
|
|
|
|
#undef CHECK
|
|
#undef OUT
|
|
|
|
/*
|
|
* We always leave enough room for the NUL if possible, but the user
|
|
* might have passed a NULL or zero-length buffer.
|
|
*/
|
|
if (out && *buflen)
|
|
*out = '\0';
|
|
|
|
*buflen = needed;
|
|
return ((rem > 0) ? 0 : -1);
|
|
}
|