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 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* 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* 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 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