Add internet address/hostname validators in tr_inet_util.[ch]
[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 <arpa/inet.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <tr_inet_util.h>
40
41 /**
42  * Determine whether a string is a valid address of a given family
43  *
44  * @param s string to check
45  * @param af address family (probably AF_INET or AF_INET6)
46  * @return 1 if string is a valid address in the given family, 0 if not, -1 on error (errno set)
47  */
48 static int is_valid_address(int af, const char *s)
49 {
50   unsigned char buf[sizeof(struct in6_addr)];
51
52   if (s == NULL)
53     return 0;
54   return inet_pton(af, s, buf);
55 }
56
57 /**
58  * Determine whether a string is a valid IPv4 address
59  * @param s string to validate
60  * @return 1 if a valid reference, 0 otherwise
61  */
62 int is_valid_ipv4_address(const char *s)
63 {
64   return is_valid_address(AF_INET, s);
65 }
66
67 /**
68  * Determine whether a string is a valid IPv6 address reference
69  *
70  * I.e., an IPv6 address in brackets
71  *
72  * @param s string to validate
73  * @return 1 if a valid reference, 0 otherwise
74  */
75 int is_valid_ipv6_reference(const char *s)
76 {
77   char *cpy;
78   size_t len;
79   int valid_ipv6;
80
81   /* check that it starts with an open bracket */
82   if (*s != '[')
83     return 0;
84
85   /* check that it ends with a close bracket */
86   len = strlen(s);
87   if (*(s+len-1) != ']')
88     return 0;
89
90   /* make a null-terminated copy of the string omitting the brackets */
91   cpy = strndup(s+1, len-2);
92   if (cpy == NULL)
93     return -1;
94
95   valid_ipv6 = is_valid_address(AF_INET6, cpy);
96   free(cpy);
97
98   return valid_ipv6;
99 }
100
101 static int is_valid_dns_char(char c)
102 {
103   /* digits ok */
104   if ( ('0' <= c) && ('9' >= c))
105     return 1;
106
107   /* letters ok */
108   if ( (('a' <= c) && ('z' >= c))
109        || (('A' <= c) && ('Z' >= c)) )
110     return 1;
111
112   /* '-' ok */
113   if ('-' == c)
114     return 1;
115
116   /* everything else illegal */
117   return 0;
118 }
119
120 /**
121  * Helper to validate a DNS label
122  *
123  * Checks whether the string starting at s[start] and ending at s[end-1]
124  * is a valid DNS label.
125  *
126  * Does not check the length of the label.
127  *
128  * @param s
129  * @param start
130  * @param end
131  * @return
132  */
133 static int is_valid_dns_label(const char *s, size_t start, size_t end)
134 {
135   size_t ii;
136
137   /* Check that there is at least one character.
138    * Be careful - size_t is unsigned */
139   if (start >= end)
140     return 0;
141
142   /* must neither start nor end with '-' */
143   if ((s[start] == '-')
144       || s[end-1] == '-')
145     return 0;
146
147   /* make sure all characters are valid */
148   for (ii=start; ii<end; ii++) {
149     if (! is_valid_dns_char(s[ii]))
150       return 0;
151   }
152
153   return 1;
154 }
155
156 /**
157  * Determine whether a string is a valid DNS name
158  *
159  * Does not check the length of the name or of the indivdiual
160  * labels in it.
161  *
162  * @param s string to validate
163  * @return 1 if a valid DNS name, 0 otherwise
164  */
165 int is_valid_dns_name(const char *s)
166 {
167   size_t label_start;
168   size_t label_end;
169
170   /* reject some trivial cases */
171   if ((s == NULL)
172       || (*s == '\0'))
173     return 0;
174
175   /* Walk along with the end counter until we encounter a '.'. When that
176    * happens, we have a complete DNS label. Validate that, then set the start
177    * counter to one character past the end pointer, which will either be the
178    * next character in the DNS name or the null terminator. Since we stop as
179    * soon as the end counter reaches a null character, this will never refer
180    * to an invalid address. */
181   for (label_start = 0, label_end = 0;
182        s[label_end] != '\0';
183        label_end++) {
184     if (s[label_end] == '.') {
185       if (! is_valid_dns_label(s, label_start, label_end))
186         return 0;
187
188       label_start = label_end+1;
189     }
190   }
191
192   if (s[label_start] == '\0')
193     return 1; /* we must have ended on a '.' */
194
195   /* There was one more label to validate */
196   return is_valid_dns_label(s, label_start, label_end);
197 }
198
199 /**
200  * Validate a host string
201  *
202  * Valid formats:
203  *   IPv4 address (dotted quad)
204  *   IPv6 address in brackets (e.g., [::1])
205  *   DNS hostname (labels made of alphanumerics or "-", separated by dots)
206  *
207  * @param s string to validate
208  * @return 1 if a valid host specification, 0 otherwise
209  */
210 int is_valid_host(const char *s)
211 {
212   if (is_valid_ipv4_address(s))
213     return 1;
214
215   if (is_valid_ipv6_reference(s))
216     return 1;
217
218   if (is_valid_dns_name(s))
219     return 1;
220
221   return 0;
222 }