1#include <arpa/inet.h>
  2
  3#include "include/pwlib/netutils.h"
  4#include "include/pwlib/parsers.h"
  5#include "include/pwlib/socket.h"
  6
  7
  8[[nodiscard]] static bool parse_addr(int domain, PwValuePtr addr, in_port_t port, PwValuePtr result)
  9{
 10    if (!pw_create(PwTypeId_SockAddr, result)) {
 11        return false;
 12    }
 13
 14    _PwSockAddrData* sa = _pw_get_struct_ptr(result, PwTypeId_SockAddr);
 15
 16    PwValue a = PW_NULL;
 17
 18    if (pw_startswith(addr, '[') && pw_endswith(addr, ']')) {
 19        if (!pw_substr(addr, 1, pw_strlen(addr) - 1, &a)) {
 20            return false;
 21        }
 22    } else {
 23        __pw_clone(&a, addr);
 24    }
 25
 26    if (domain == AF_UNSPEC && pw_strchr(&a, ':', 0, nullptr)) {
 27        domain = AF_INET6;
 28    } else {
 29        domain = AF_INET;
 30    }
 31
 32    PW_CSTRING(c_addr, &a);
 33    int rc;
 34    switch (domain) {
 35        case AF_INET: {
 36            struct sockaddr_in* sin = (struct sockaddr_in*) &sa->addr;
 37            sin->sin_family = domain;
 38            sin->sin_port = htons(port);
 39            rc = inet_pton(AF_INET, c_addr, &sin->sin_addr);
 40            break;
 41        }
 42        case AF_INET6: {
 43            struct sockaddr_in6* sin = (struct sockaddr_in6*) &sa->addr;
 44            sin->sin6_family = domain;
 45            sin->sin6_port = htons(port);
 46            rc = inet_pton(AF_INET, c_addr, &sin->sin6_addr);
 47            break;
 48        }
 49        default:
 50            pw_set_status(PwStatus(PweBadAddressFamily));
 51            return false;
 52    }
 53    if (rc == -1) {
 54        pw_set_status(PwStatus(PweBadAddressFamily));
 55        return false;
 56    }
 57    if (rc != 1) {
 58        pw_set_status(PwStatus(PweBadIpAddress));
 59        return false;
 60    }
 61    return true;
 62}
 63
 64[[nodiscard]] bool _pw_parse_inet_address(PwValuePtr addr, PwValuePtr result)
 65{
 66    pw_assert(pw_is_string(addr));
 67
 68    // try to separate address and port parts
 69    PwValue parts = PW_NULL;
 70    if (!pw_string_rsplit_chr(addr, ':', 1, &parts)) {
 71        return false;
 72    }
 73    if (pw_array_length(&parts) == 1) {
 74        // addr without port
 75        return parse_addr(AF_UNSPEC, addr, 0, result);
 76    }
 77
 78    PwValue addr_part = PW_NULL;
 79    if (!pw_array_item(&parts, 0, &addr_part)) {
 80        return false;
 81    }
 82    PwValue port_part = PW_NULL;
 83    if (!pw_array_item(&parts, 1, &port_part)) {
 84        return false;
 85    }
 86    if (pw_strchr(&addr_part, ':', 0, nullptr)) {
 87        // IPv6 address?
 88        if (pw_startswith(&addr_part, '[') && pw_endswith(&addr_part, ']')) {
 89            // yes, with port
 90        } else {
 91            // yes, without port
 92            // parse original addr as IPv6
 93            return parse_addr(AF_INET6, addr, 0, result);
 94        }
 95    }
 96
 97    // parse port
 98    PwValue port = PW_NULL;
 99    if (!pw_parse_number(&port_part, &port)) {
100        return false;
101    }
102    if ( ! (pw_is_signed(&port) && port.signed_value > 0 && port.signed_value < 65536)) {
103        pw_set_status(PwStatus(PweBadPort));
104        return false;
105    }
106
107    // parse address
108    return parse_addr(AF_UNSPEC, &addr_part, port.signed_value, result);
109}
110
111[[nodiscard]] bool pw_parse_subnet(PwValuePtr subnet, PwValuePtr netmask, PwValuePtr result)
112{
113    pw_assert(pw_is_string(subnet));
114
115    // check CIDR notation
116    PwValue parts = PW_NULL;
117    if (!pw_string_split_chr(subnet, '/', 0, &parts)) {
118        return false;
119    }
120
121    unsigned num_parts = pw_array_length(&parts);
122    if (num_parts > 2) {
123        pw_set_status(PwStatus(PweBadNetmask));
124        return false;
125    }
126
127    // parse subnet address
128    PwValue subnet_addr = PW_NULL;
129    if (!pw_array_item(&parts, 0, &subnet_addr)) {
130        return false;
131    }
132    if (!pw_parse_inet_address(&subnet_addr, result)) {
133        return false;
134    }
135
136    if (num_parts > 1) {
137
138        // try CIDR netmask
139        PwValue cidr_netmask = PW_NULL;
140        if (!pw_array_item(&parts, 1, &cidr_netmask)) {
141            return false;
142        }
143        PwValue n = PW_NULL;
144        if (!pw_parse_number(&cidr_netmask, &n)) {
145            return false;
146        }
147        if (!pw_is_signed(&n)) {
148            pw_set_status(PwStatus(PweBadNetmask));
149            return false;
150        }
151        if (n.signed_value == 0) {
152            pw_set_status(PwStatus(PweBadNetmask));
153            return false;
154        }
155
156        // check upper range depending on address family
157        _PwSockAddrData* sa = _pw_get_struct_ptr(result, PwTypeId_SockAddr);
158        int max_bits;
159        if (sa->addr.ss_family == AF_INET) {
160            max_bits = 32;
161        } else {
162            max_bits = 128;
163        }
164        if (n.signed_value >= max_bits) {
165            pw_set_status(PwStatus(PweBadNetmask));
166            return false;
167        }
168
169        // set netmask in the result
170        sa->netmask = n.signed_value;
171
172    } else {
173        // not CIDR notation, parse netmask parameter
174
175        if (pw_is_null(netmask)) {
176            pw_set_status(PwStatus(PweMissingNetmask));
177            return false;
178        }
179
180        pw_assert(pw_is_string(netmask));
181
182        PwValue parsed_netmask = PW_NULL;
183        if (!pw_parse_inet_address(netmask, &parsed_netmask)) {
184            return false;
185        }
186
187        // check address families are same
188        _PwSockAddrData* sa_result  = _pw_get_struct_ptr(result, PwTypeId_SockAddr);
189        _PwSockAddrData* sa_netmask = _pw_get_struct_ptr(&parsed_netmask, PwTypeId_SockAddr);
190
191        if (sa_result->addr.ss_family != sa_netmask->addr.ss_family) {
192            pw_set_status(PwStatus(PweBadNetmask));
193            return false;
194        }
195
196        // convert netmask to the number of bits
197        if (sa_result->addr.ss_family == AF_INET) {
198            unsigned n = ntohl(((struct sockaddr_in*) &sa_netmask->addr)->sin_addr.s_addr);
199            static_assert(sizeof(n) >= 4);
200            if (n == 0) {
201                sa_result->netmask = 0;
202            } else {
203                // XXX use stdc_trailing_zeros
204                sa_result->netmask = 32 - __builtin_ctz(n);
205            }
206        } else {
207            uint8_t* addr = ((struct sockaddr_in6*) &sa_netmask->addr)->sin6_addr.s6_addr;
208            sa_result->netmask = 128;
209            addr += 15;
210            for (unsigned i = 0; i < 16; i++) {
211                uint8_t n = *addr--;
212                if (n) {
213                    // XXX use stdc_trailing_zeros
214                    sa_result->netmask -= __builtin_ctz(n);
215                    break;
216                }
217                sa_result->netmask -= 8;
218            }
219        }
220    }
221    return true;
222}
223
224[[nodiscard]] bool _pw_split_addr_port(PwValuePtr addr_port, PwValuePtr addr, PwValuePtr port)
225{
226    PwValue emptystr = PW_STRING();
227    PwValue parts = PW_NULL;
228    if (!pw_string_rsplit_chr(addr_port, ':', 1, &parts)) {
229        return false;
230    }
231    if (pw_array_length(&parts) == 1) {
232        // Assume addr_port contains port (or service name) only.
233        pw_move(addr, &emptystr);
234        if (!pw_array_item(&parts, 0, port)) {
235            return false;
236        }
237    } else {
238        if (!pw_array_item(&parts, 0, addr)) {
239            return false;
240        }
241        if (!pw_array_item(&parts, 1, port)) {
242            return false;
243        }
244        if (pw_strchr(addr, ':', 0, nullptr)) {
245            // IPv6 address?
246            if (pw_startswith(addr, '[') && pw_endswith(addr, ']')) {
247                // address is in square brackets, so port is okay
248            } else {
249                // port is missing
250                pw_clone2(addr, addr_port);
251                pw_clone2(port, &emptystr);
252            }
253        }
254    }
255    return true;
256}