Fix handling of errors with strtol(), factor out port parsing
[trust_router.git] / common / tr_inet_util.c
1 /*
2  * Copyright (c) 2018, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34
35 #include <tr_name_internal.h>
36 #include <arpa/inet.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <talloc.h>
40
41 #include <tr_inet_util.h>
42 #include <errno.h>
43
44 /**
45  * Determine whether a string is a valid address of a given family
46  *
47  * @param s string to check
48  * @param af address family (probably AF_INET or AF_INET6)
49  * @return 1 if string is a valid address in the given family, 0 if not, -1 on error (errno set)
50  */
51 static int is_valid_address(int af, const char *s)
52 {
53   unsigned char buf[sizeof(struct in6_addr)];
54
55   if (s == NULL)
56     return 0;
57   return inet_pton(af, s, buf);
58 }
59
60 /**
61  * Determine whether a string is a valid IPv6 address reference
62  *
63  * I.e., an IPv6 address in brackets
64  *
65  * @param s string to validate
66  * @return 1 if a valid reference, 0 otherwise
67  */
68 static int tr_valid_ipv6_reference(const char *s)
69 {
70   char *cpy;
71   size_t len;
72   int valid_ipv6;
73
74   /* check that it starts with an open bracket */
75   if (*s != '[')
76     return 0;
77
78   /* check that it ends with a close bracket */
79   len = strlen(s);
80   if (*(s+len-1) != ']')
81     return 0;
82
83   /* make a null-terminated copy of the string omitting the brackets */
84   cpy = talloc_strndup(NULL, s+1, len-2);
85   if (cpy == NULL)
86     return 0; /* an error occurred - fail safe */
87
88   valid_ipv6 = is_valid_address(AF_INET6, cpy);
89   talloc_free(cpy);
90
91   return valid_ipv6;
92 }
93
94 /**
95  * Validate a host string
96  *
97  * The intention is to reject strings that may appear to contain a ':port' spec.
98  * Takes a permissive view of valid: a hostname is valid if either it is a
99  * bracketed IPv6 address reference ([address]) or has no brackets or colons.
100  * This accepts all valid DNS names and IPv4 addresses, as well as many invalid
101  * hostnames. This is ok for accepting a hostname that will later be resolved
102  * because invalid names will fail to resolve. It should *not* be used to ensure
103  * a hostname is compliant with RFC!
104  *
105  * Ignores a trailing colon followed by decimal digits.
106  *
107  * @param s string to validate
108  * @return 1 if a valid host specification, 0 otherwise
109  */
110 static int tr_valid_host(const char *s)
111 {
112   if (strchr(s, '[') || strchr(s, ']') || strchr(s, ':'))
113     return tr_valid_ipv6_reference(s);
114
115   return 1;
116 }
117
118 /**
119  * Check that all characters are decimal digits
120  *
121  * @param s
122  * @return 1 if all digits, 0 otherwise
123  */
124 static int tr_str_all_digits(const char *s)
125 {
126   if (s == NULL)
127     return 0;
128
129   for( ; *s; s++) {
130     if ( (*s < '0') || (*s > '9'))
131       return 0;
132   }
133
134   return 1;
135 }
136
137 /**
138  * Validate and parse a hostname or hostname/port
139  *
140  * If port_out is not null, accepts a port as well. This is
141  * stored in *port_out. If no port is given, a 0 is stored.
142  * If an invalid port is given, -1 is stored.
143  *
144  * If the hostname is invalid, null is returned and no value
145  * is written to *port_out.
146  *
147  * If port_out is null, null will be returned if the string
148  * contains a port.
149  *
150  * The return value must be freed with talloc_free unless
151  * it is null.
152  *
153  * @param mem_ctx talloc context for hostname result
154  * @param s string to parse
155  * @param port_out pointer to an allocated integer, or NULL
156  * @return pointer to the hostname or null on error
157  */
158 char *tr_parse_host(TALLOC_CTX *mem_ctx, const char *s, int *port_out)
159 {
160   const char *colon;
161   char *hostname;
162   int port;
163
164   if (s == NULL)
165     return NULL;
166
167   /* If we are accepting a port, find the last colon. */
168   if (port_out == NULL)
169     colon = NULL;
170   else
171     colon = strrchr(s, ':');
172
173   /* Get a copy of the hostname portion, which may be the entire string. */
174   if (colon == NULL)
175     hostname = talloc_strdup(NULL, s);
176   else
177     hostname = talloc_strndup(NULL, s, colon-s);
178
179   if (hostname == NULL)
180     return NULL; /* failed to dup the hostname */
181
182   /* Check that the hostname is valid; if not, return null and ignore the port. */
183   if (! tr_valid_host(hostname)) {
184     talloc_free(hostname);
185     return NULL;
186   }
187
188   /* If we are accepting a port, parse and validate it. */
189   if (port_out != NULL) {
190     if (colon == NULL) {
191       *port_out = 0;
192     } else {
193       port = tr_parse_port(colon+1);
194       if ((port > 0) && tr_str_all_digits(colon+1))
195         *port_out = port;
196       else
197         *port_out = -1;
198     }
199   }
200
201   return hostname;
202 }
203
204 TR_NAME *tr_hostname_and_port_to_name(TR_NAME *hn, int port)
205 {
206   TR_NAME *retval = NULL;
207   char *s = NULL;
208   char *hn_s = tr_name_strdup(hn);
209
210   if (!hn_s)
211     return NULL;
212
213   s = talloc_asprintf(NULL, "%s:%d", hn_s, port);
214   free(hn_s);
215
216   if (s) {
217     retval = tr_new_name(s);
218     talloc_free(s);
219   }
220
221   return retval;
222 }
223
224 /**
225  * Parse a string containing a port
226  *
227  * Returns the port number, which is always in the range 1-65535.
228  * On error, returns < 0. The absolute value is an error code from errno.h
229  *
230  * @param s
231  * @return port number or < 0 on error
232  */
233 int tr_parse_port(const char *s)
234 {
235   long port;
236   char *end;
237
238   errno = 0; /* strtol sets this, make sure it's zero to avoid false positives */
239   port = strtol(s, &end, 10);
240   if (errno) {
241     return -errno;
242   }
243
244   if (*end != '\0') {
245     return -EINVAL;
246   }
247
248   if ((port <= 0) || (port > 65535)) {
249     return -ERANGE;
250   }
251
252   return (int) port;
253 }