1*50c1baceSchristos /* $NetBSD: decodenetnum.c,v 1.7 2022/10/09 21:41:03 christos Exp $ */
2abb0f93cSkardel
3abb0f93cSkardel /*
4abb0f93cSkardel * decodenetnum - return a net number (this is crude, but careful)
5abb0f93cSkardel */
6abb0f93cSkardel #include <config.h>
7abb0f93cSkardel #include <sys/types.h>
8abb0f93cSkardel #include <ctype.h>
9abb0f93cSkardel #ifdef HAVE_SYS_SOCKET_H
10abb0f93cSkardel #include <sys/socket.h>
11abb0f93cSkardel #endif
12abb0f93cSkardel #ifdef HAVE_NETINET_IN_H
13abb0f93cSkardel #include <netinet/in.h>
14abb0f93cSkardel #endif
15abb0f93cSkardel
16f003fb54Skardel #include "ntp.h"
17abb0f93cSkardel #include "ntp_stdlib.h"
18abb0f93cSkardel
19cdfa2a7eSchristos
20*50c1baceSchristos /* If the given string position points to a decimal digit, parse the
21*50c1baceSchristos * number. If this is not possible, or the parsing did not consume the
22*50c1baceSchristos * whole string, or if the result exceeds the maximum value, return the
23*50c1baceSchristos * default value.
24*50c1baceSchristos */
25*50c1baceSchristos static unsigned long
_num_or_dflt(char * sval,unsigned long maxval,unsigned long defval)26*50c1baceSchristos _num_or_dflt(
27*50c1baceSchristos char * sval,
28*50c1baceSchristos unsigned long maxval,
29*50c1baceSchristos unsigned long defval
30cdfa2a7eSchristos )
31cdfa2a7eSchristos {
32*50c1baceSchristos char * ep;
33*50c1baceSchristos unsigned long num;
34*50c1baceSchristos
35*50c1baceSchristos if (!(sval && isdigit(*(unsigned char*)sval)))
36*50c1baceSchristos return defval;
37*50c1baceSchristos
38*50c1baceSchristos num = strtoul(sval, &ep, 10);
39*50c1baceSchristos if (!*ep && num <= maxval)
40*50c1baceSchristos return num;
41*50c1baceSchristos
42*50c1baceSchristos return defval;
43*50c1baceSchristos }
44*50c1baceSchristos
45*50c1baceSchristos /* If the given string position is not NULL and does not point to the
46*50c1baceSchristos * terminator, replace the character with NUL and advance the pointer.
47*50c1baceSchristos * Return the resulting position.
48*50c1baceSchristos */
49*50c1baceSchristos static inline char*
_chop(char * sp)50*50c1baceSchristos _chop(
51*50c1baceSchristos char * sp)
52*50c1baceSchristos {
53*50c1baceSchristos if (sp && *sp)
54*50c1baceSchristos *sp++ = '\0';
55*50c1baceSchristos return sp;
56*50c1baceSchristos }
57*50c1baceSchristos
58*50c1baceSchristos /* If the given string position points to the given char, advance the
59*50c1baceSchristos * pointer and return the result. Otherwise, return NULL.
60*50c1baceSchristos */
61*50c1baceSchristos static inline char*
_skip(char * sp,int ch)62*50c1baceSchristos _skip(
63*50c1baceSchristos char * sp,
64*50c1baceSchristos int ch)
65*50c1baceSchristos {
66*50c1baceSchristos if (sp && *(unsigned char*)sp == ch)
67*50c1baceSchristos return (sp + 1);
68*50c1baceSchristos return NULL;
69cdfa2a7eSchristos }
70cdfa2a7eSchristos
71f003fb54Skardel /*
72f003fb54Skardel * decodenetnum convert text IP address and port to sockaddr_u
73f003fb54Skardel *
74*50c1baceSchristos * Returns FALSE (->0) for failure, TRUE (->1) for success.
75f003fb54Skardel */
76abb0f93cSkardel int
decodenetnum(const char * num,sockaddr_u * net)77abb0f93cSkardel decodenetnum(
78abb0f93cSkardel const char *num,
79*50c1baceSchristos sockaddr_u *net
80abb0f93cSkardel )
81abb0f93cSkardel {
82*50c1baceSchristos /* Building a parser is more fun in Haskell, but here we go...
83*50c1baceSchristos *
84*50c1baceSchristos * This works through 'inet_pton()' taking the brunt of the
85*50c1baceSchristos * work, after some string manipulations to split off URI
86*50c1baceSchristos * brackets, ports and scope identifiers. The heuristics are
87*50c1baceSchristos * simple but must hold for all _VALID_ addresses. inet_pton()
88*50c1baceSchristos * will croak on bad ones later, but replicating the whole
89*50c1baceSchristos * parser logic to detect errors is wasteful.
90f003fb54Skardel */
91*50c1baceSchristos
92*50c1baceSchristos sockaddr_u netnum;
93*50c1baceSchristos char buf[64]; /* working copy of input */
94*50c1baceSchristos char *haddr=buf;
95*50c1baceSchristos unsigned int port=NTP_PORT, scope=0;
96*50c1baceSchristos unsigned short afam=AF_UNSPEC;
97*50c1baceSchristos
98*50c1baceSchristos /* copy input to working buffer with length check */
99*50c1baceSchristos if (strlcpy(buf, num, sizeof(buf)) >= sizeof(buf))
100*50c1baceSchristos return FALSE;
101*50c1baceSchristos
102*50c1baceSchristos /* Identify address family and possibly the port, if given. If
103*50c1baceSchristos * this results in AF_UNSPEC, we will fail in the next step.
104*50c1baceSchristos */
105*50c1baceSchristos if (*haddr == '[') {
106*50c1baceSchristos char * endp = strchr(++haddr, ']');
107*50c1baceSchristos if (endp) {
108*50c1baceSchristos port = _num_or_dflt(_skip(_chop(endp), ':'),
109*50c1baceSchristos 0xFFFFu, port);
110*50c1baceSchristos afam = strchr(haddr, ':') ? AF_INET6 : AF_INET;
111f003fb54Skardel }
112f003fb54Skardel } else {
113*50c1baceSchristos char *col = strchr(haddr, ':');
114*50c1baceSchristos char *dot = strchr(haddr, '.');
115*50c1baceSchristos if (col == dot) {
116*50c1baceSchristos /* no dot, no colon: bad! */
117*50c1baceSchristos afam = AF_UNSPEC;
118*50c1baceSchristos } else if (!col) {
119*50c1baceSchristos /* no colon, only dot: IPv4! */
120*50c1baceSchristos afam = AF_INET;
121*50c1baceSchristos } else if (!dot || col < dot) {
122*50c1baceSchristos /* no dot or 1st colon before 1st dot: IPv6! */
123*50c1baceSchristos afam = AF_INET6;
124*50c1baceSchristos } else {
125*50c1baceSchristos /* 1st dot before 1st colon: must be IPv4 with port */
126*50c1baceSchristos afam = AF_INET;
127*50c1baceSchristos port = _num_or_dflt(_chop(col), 0xFFFFu, port);
128abb0f93cSkardel }
129*50c1baceSchristos }
130*50c1baceSchristos
131*50c1baceSchristos /* Since we don't know about additional members in the address
132*50c1baceSchristos * structures, we wipe the result buffer thoroughly:
133*50c1baceSchristos */
134*50c1baceSchristos memset(&netnum, 0, sizeof(netnum));
135*50c1baceSchristos
136*50c1baceSchristos /* For AF_INET6, evaluate and remove any scope suffix. Have
137*50c1baceSchristos * inet_pton() do the real work for AF_INET and AF_INET6, bail
138*50c1baceSchristos * out otherwise:
139*50c1baceSchristos */
140*50c1baceSchristos switch (afam) {
141*50c1baceSchristos case AF_INET:
142*50c1baceSchristos if (inet_pton(afam, haddr, &netnum.sa4.sin_addr) <= 0)
143cdfa2a7eSchristos return FALSE;
144*50c1baceSchristos netnum.sa4.sin_port = htons((unsigned short)port);
145*50c1baceSchristos break;
146cdfa2a7eSchristos
147*50c1baceSchristos case AF_INET6:
148*50c1baceSchristos scope = _num_or_dflt(_chop(strchr(haddr, '%')), 0xFFFFFFFFu, scope);
149*50c1baceSchristos if (inet_pton(afam, haddr, &netnum.sa6.sin6_addr) <= 0)
150cdfa2a7eSchristos return FALSE;
151*50c1baceSchristos netnum.sa6.sin6_port = htons((unsigned short)port);
152*50c1baceSchristos netnum.sa6.sin6_scope_id = scope;
153*50c1baceSchristos break;
154cdfa2a7eSchristos
155*50c1baceSchristos case AF_UNSPEC:
156*50c1baceSchristos default:
157*50c1baceSchristos return FALSE;
158*50c1baceSchristos }
159cdfa2a7eSchristos
160*50c1baceSchristos /* Collect the remaining pieces and feed the output, which was
161*50c1baceSchristos * not touched so far:
162*50c1baceSchristos */
163*50c1baceSchristos netnum.sa.sa_family = afam;
164*50c1baceSchristos memcpy(net, &netnum, sizeof(netnum));
165cdfa2a7eSchristos return TRUE;
166abb0f93cSkardel }
167