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