blob: 0dc9972ab51a968ef7bc4d37ea9d043f32200330 [file] [log] [blame]
Brian Waters13d96012017-12-08 16:53:31 -06001/*********************************************************************************************************
2* Software License Agreement (BSD License) *
3* Author: Sebastien Decugis <sdecugis@freediameter.net> *
4* *
5* Copyright (c) 2013, WIDE Project and NICT *
6* All rights reserved. *
7* *
8* Redistribution and use of this software in source and binary forms, with or without modification, are *
9* permitted provided that the following conditions are met: *
10* *
11* * Redistributions of source code must retain the above *
12* copyright notice, this list of conditions and the *
13* following disclaimer. *
14* *
15* * Redistributions in binary form must reproduce the above *
16* copyright notice, this list of conditions and the *
17* following disclaimer in the documentation and/or other *
18* materials provided with the distribution. *
19* *
20* * Neither the name of the WIDE Project or NICT nor the *
21* names of its contributors may be used to endorse or *
22* promote products derived from this software without *
23* specific prior written permission of WIDE Project and *
24* NICT. *
25* *
26* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
27* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
28* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
29* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
30* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
31* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
32* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
33* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
34*********************************************************************************************************/
35
36#include "fdproto-internal.h"
37
38#if (!defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT))
39/* Process IDNA with stringprep -- See RFC5890 -- and libidn documentation... */
40#include <idna.h> /* idna_to_ascii_8z() */
41#endif /* !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT) */
42
43/* Similar to strdup with (must have been verified) os0_t */
44os0_t os0dup_int(os0_t s, size_t l) {
45 os0_t r;
46 CHECK_MALLOC_DO( r = malloc(l+1), return NULL );
47 if (l)
48 memcpy(r, s, l); /* this might be faster than a strcpy or strdup because it can work with 32 or 64b blocks */
49 r[l] = '\0';
50 return r;
51}
52
53/* case sensitive comparison, fast */
54int fd_os_cmp_int(uint8_t * os1, size_t os1sz, uint8_t * os2, size_t os2sz)
55{
56 ASSERT( os1 && os2);
57 if (os1sz < os2sz)
58 return -1;
59 if (os1sz > os2sz)
60 return 1;
61 return os1sz ? memcmp(os1, os2, os1sz) : 0;
62}
63
64/* a local version of tolower() that does not depend on LC_CTYPE locale */
65static inline uint8_t asciitolower(uint8_t a)
66{
67 if ((a >= 'A') && (a <= 'Z'))
68 return a + 32 /* == 'a' - 'A' */;
69 return a;
70}
71
72/* less sensitive to case, slower. */
73/* the semantics of "maybefurther" assume you are searching for os1 in a list of elements ordered, each element passed as os2 */
74int fd_os_almostcasesrch_int(uint8_t * os1, size_t os1sz, uint8_t * os2, size_t os2sz, int *maybefurther)
75{
76 int i;
77 int res = 0;
78
79 ASSERT( os1 && os2);
80 if (maybefurther)
81 *maybefurther = 0;
82
83 if (os1sz < os2sz)
84 return -1;
85
86 if (maybefurther)
87 *maybefurther = 1;
88
89 if (os1sz > os2sz)
90 return 1;
91
92 for (i = 0; i < os1sz; i++) {
93 if (os1[i] == os2[i])
94 continue;
95
96 if (!res)
97 res = os1[i] < os2[i] ? -1 : 1;
98
99 if (asciitolower(os1[i]) == asciitolower(os2[i]))
100 continue;
101
102 return res;
103 }
104
105 return 0;
106}
107
108/* Check if the string contains only ASCII */
109int fd_os_is_valid_DiameterIdentity(uint8_t * os, size_t ossz)
110{
111#ifdef DIAMID_IDNA_IGNORE
112
113 /* Allow anything */
114
115#else /* DIAMID_IDNA_IGNORE */
116
117 int i;
118
119 /* Allow only letters, digits, hyphen, dot */
120 for (i=0; i < ossz; i++) {
121 if (os[i] > 'z')
122 break;
123 if (os[i] >= 'a')
124 continue;
125 if ((os[i] >= 'A') && (os[i] <= 'Z'))
126 continue;
127 if ((os[i] == '-') || (os[i] == '.'))
128 continue;
129 if ((os[i] >= '0') && (os[i] <= '9'))
130 continue;
131 break;
132 }
133 if (i < ossz) {
134 int nb = 1;
135 /* To get a better display, check if the invalid char is UTF-8 */
136 if ((os[i] & 0xE0) == 0xC0 /* 110xxxxx */) {
137 if ((i < ossz - 1) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */))
138 nb = 2;
139 goto disp;
140 }
141 if ((os[i] & 0xF0) == 0xE0 /* 1110xxxx */) {
142 if ((i < ossz - 2) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
143 && ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */))
144 nb = 3;
145 goto disp;
146 }
147 if ((os[i] & 0xF8) == 0xF0 /* 11110xxx */) {
148 if ((i < ossz - 3) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
149 && ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
150 && ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */))
151 nb = 4;
152 goto disp;
153 }
154 if ((os[i] & 0xFC) == 0xF8 /* 111110xx */) {
155 if ((i < ossz - 4) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
156 && ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
157 && ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */)
158 && ((os[i + 4] & 0xC0) == 0x80 /* 10xxxxxx */))
159 nb = 5;
160 goto disp;
161 }
162 if ((os[i] & 0xFE) == 0xFC /* 1111110x */) {
163 if ((i < ossz - 5) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
164 && ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
165 && ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */)
166 && ((os[i + 4] & 0xC0) == 0x80 /* 10xxxxxx */)
167 && ((os[i + 5] & 0xC0) == 0x80 /* 10xxxxxx */))
168 nb = 6;
169 goto disp;
170 }
171 /* otherwise, we just display the hex code */
172 TRACE_DEBUG(INFO, "Invalid character (0x%hhX) at offset %d in DiameterIdentity '%.*s'", os[i], i+1, (int)ossz, os);
173 return 0;
174disp:
175 TRACE_DEBUG(INFO, "Invalid character '%.*s' at offset %d in DiameterIdentity '%.*s'", nb, os + i, i+1, (int)ossz, os);
176 return 0;
177 }
178
179#endif /* DIAMID_IDNA_IGNORE */
180
181 return 1;
182}
183
184/* The following function validates a string as a Diameter Identity or applies the IDNA transformation on it
185 if *inoutsz is != 0 on entry, *id may not be \0-terminated.
186 memory has the following meaning: 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static)
187*/
188int fd_os_validate_DiameterIdentity(char ** id, size_t * inoutsz, int memory)
189{
190#if !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT)
191 int gotsize = 0;
192#endif /* defined(DIAMID_IDNA_IGNORE) || defined(DIAMID_IDNA_REJECT) */
193
194 TRACE_ENTRY("%p %p", id, inoutsz);
195 CHECK_PARAMS( id && *id && inoutsz );
196
197 if (!*inoutsz)
198 *inoutsz = strlen(*id);
199#if !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT)
200 else
201 gotsize = 1;
202#endif /* defined(DIAMID_IDNA_IGNORE) || defined(DIAMID_IDNA_REJECT) */
203
204#ifndef DIAMID_IDNA_IGNORE
205
206 if (!fd_os_is_valid_DiameterIdentity((os0_t)*id, *inoutsz)) {
207
208#ifdef DIAMID_IDNA_REJECT
209
210 TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity!", *id);
211 TRACE_DEBUG(INFO, "Returning EINVAL since fD is compiled with option DIAMID_IDNA_REJECT.");
212 return EINVAL;
213
214#else /* DIAMID_IDNA_REJECT */
215
216 char *processed;
217 int ret;
218
219 if (gotsize) { /* make it \0-terminated */
220 if (memory) {
221 CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
222 memory = 0;
223 } else {
224 CHECK_MALLOC( *id = realloc(*id, *inoutsz + 1) );
225 (*id)[*inoutsz] = '0';
226 }
227 }
228
229 ret = idna_to_ascii_8z ( *id, &processed, IDNA_USE_STD3_ASCII_RULES );
230 if (ret == IDNA_SUCCESS) {
231 TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity, it was changed to '%s'", *id, processed);
232 if (memory == 0)
233 free(*id);
234 *id = processed;
235 *inoutsz = strlen(processed);
236 /* Done! */
237 } else {
238 TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity and cannot be sanitanized: %s", *id, idna_strerror (ret));
239 return EINVAL;
240 }
241
242#endif /* DIAMID_IDNA_REJECT */
243 } else
244#endif /* ! DIAMID_IDNA_IGNORE */
245 {
246 if (memory == 1) {
247 CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
248 }
249 }
250 return 0;
251}
252
253/* Analyze a DiameterURI and return its components.
254 Return EINVAL if the URI is not valid.
255 *diamid is malloc'd on function return and must be freed (it is processed by fd_os_validate_DiameterIdentity).
256 *secure is 0 (no security) or 1 (security enabled) on return.
257 *port is 0 (default) or a value in host byte order on return.
258 *transport is 0 (default) or IPPROTO_* on return.
259 *proto is 0 (default) or 'd' (diameter), 'r' (radius), or 't' (tacacs+) on return.
260 */
261int fd_os_parse_DiameterURI(uint8_t * uri, size_t urisz, DiamId_t * diamid, size_t * diamidlen, int * secure, uint16_t * port, int * transport, char *proto)
262{
263 size_t offset = 0;
264 DiamId_t fqdn = NULL;
265 size_t fqdnlen;
266 TRACE_ENTRY("%p %zd %p %p %p %p %p %p", uri, urisz, diamid, diamidlen, secure, port, transport, proto);
267 CHECK_PARAMS( uri && urisz );
268
269 CHECK_PARAMS( urisz > 7 ); /* "aaa" + "://" + something else at least */
270
271 /* Initialize values */
272 if (secure)
273 *secure = 0;
274 if (port)
275 *port = 0;
276 if (transport)
277 *transport = 0;
278 if (proto)
279 *proto = 0;
280
281 /* Check the beginning */
282 if (memcmp( uri, "aaa", 3)) {
283 TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa'", 3, uri);
284 return EINVAL;
285 }
286 offset += 3;
287
288 /* Secure? */
289 if (uri[offset] == (uint8_t)'s') {
290 if (secure)
291 *secure = 1;
292 offset += 1;
293 }
294
295 /* Remaining of URI marker */
296 if (memcmp( uri + offset, "://", 3)) {
297 TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa://' or 'aaas://'", (int)offset + 3, uri);
298 return EINVAL;
299 }
300 offset += 3;
301
302 /* This is the start of the FQDN */
303 fqdn = (DiamId_t)uri + offset;
304 for ( ; offset < urisz ; offset++ ) {
305 /* Stop only when we find ':' or ';' */
306 if ((uri[offset] == (uint8_t)':') || (uri[offset] == (uint8_t)';'))
307 break;
308 }
309 fqdnlen = offset - (fqdn - (DiamId_t)uri);
310 CHECK_FCT(fd_os_validate_DiameterIdentity(&fqdn, &fqdnlen, 1));
311 if (diamid)
312 *diamid = fqdn;
313 else
314 free(fqdn);
315 if (diamidlen)
316 *diamidlen = fqdnlen;
317
318 if (offset == urisz)
319 return 0; /* Finished */
320
321 /* Is there a port ? */
322 if (uri[offset] == ':') {
323 uint16_t p = 0;
324 do {
325 offset++;
326
327 if (offset == urisz)
328 break;
329
330 uint32_t t = (uint32_t)((char)uri[offset] - '0');
331 if (t > 9)
332 break; /* we did not get a digit */
333
334 t += p * 10; /* the port is specified in decimal base */
335
336 if (t >= (1<<16)) {
337 TRACE_DEBUG(INFO, "Invalid DiameterURI: port value is too big.");
338 return EINVAL;
339 }
340
341 p = t;
342 } while (1);
343
344 if (port)
345 *port = p;
346 }
347
348 if (offset == urisz)
349 return 0; /* Finished */
350
351 /* Is there a transport? */
352 if ( (urisz - offset > CONSTSTRLEN(";transport="))
353 && !strncasecmp((char *)uri + offset, ";transport=", CONSTSTRLEN(";transport=")) ) {
354
355 offset += CONSTSTRLEN(";transport=");
356
357 if (urisz - offset < 3) {
358 TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is too short, ignored.");
359 return 0;
360 }
361 if (!strncasecmp((char *)uri + offset, "tcp", CONSTSTRLEN("tcp"))) {
362 if (transport)
363 *transport = IPPROTO_TCP;
364 offset += CONSTSTRLEN("tcp");
365 goto after_transport;
366 }
367 if (!strncasecmp((char *)uri + offset, "udp", CONSTSTRLEN("udp"))) {
368 if (transport)
369 *transport = IPPROTO_UDP;
370 offset += CONSTSTRLEN("udp");
371 goto after_transport;
372 }
373 if ((urisz - offset > 3) && !strncasecmp((char *)uri + offset, "sctp", CONSTSTRLEN("sctp"))) {
374 if (transport) {
375#ifndef DISABLE_SCTP
376 *transport = IPPROTO_SCTP;
377#else /* DISABLE_SCTP */
378 TRACE_DEBUG(INFO, "Received DiameterURI with 'transport=sctp' but DISABLE_SCTP was selected");
379 *transport = 0;
380#endif /* DISABLE_SCTP */
381 }
382 offset += CONSTSTRLEN("sctp");
383 goto after_transport;
384 }
385
386 TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
387 return EINVAL;
388 }
389after_transport:
390 if (offset == urisz)
391 return 0; /* Finished */
392
393 /* Is there a protocol? */
394 if ( ((urisz - offset) > CONSTSTRLEN(";protocol="))
395 && (!strncasecmp((char *)uri + offset, ";protocol=", CONSTSTRLEN(";protocol="))) ) {
396
397 offset += CONSTSTRLEN(";protocol=");
398
399 if ( ((urisz - offset) >= CONSTSTRLEN("diameter"))
400 && (!strncasecmp((char *)uri + offset, "diameter", CONSTSTRLEN("diameter"))) ) {
401 if (proto)
402 *proto = 'd';
403 offset += CONSTSTRLEN("diameter");
404 goto after_proto;
405 }
406
407 if ( ((urisz - offset) >= CONSTSTRLEN("radius"))
408 && (!strncasecmp((char *)uri + offset, "radius", CONSTSTRLEN("radius"))) ) {
409 if (proto)
410 *proto = 'r';
411 offset += CONSTSTRLEN("radius");
412 goto after_proto;
413 }
414
415 if ( ((urisz - offset) >= CONSTSTRLEN("tacacs+"))
416 && (!strncasecmp((char *)uri + offset, "tacacs+", CONSTSTRLEN("tacacs+"))) ) {
417 if (proto)
418 *proto = 't';
419 offset += CONSTSTRLEN("tacacs+");
420 goto after_proto;
421 }
422
423 TRACE_DEBUG(INFO, "Invalid DiameterURI: protocol string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
424 return EINVAL;
425
426 }
427after_proto:
428 if (offset == urisz)
429 return 0; /* Finished */
430
431 TRACE_DEBUG(INFO, "Invalid DiameterURI: final part of string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
432 return EINVAL;
433}
434
435
436/********************************************************************************************************/
437/* Hash function -- credits to Austin Appleby, thank you ^^ */
438/* See http://murmurhash.googlepages.com for more information on this function */
439
440/* the strings are NOT always aligned properly (ex: received in RADIUS message), so we use the aligned MurmurHash2 function as needed */
441#define _HASH_MIX(h,k,m) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
442uint32_t fd_os_hash ( uint8_t * string, size_t len )
443{
444 uint32_t hash = len;
445 uint8_t * data = string;
446
447 const unsigned int m = 0x5bd1e995;
448 const int r = 24;
449 int align = (long)string & 3;
450
451 if (!align || (len < 4)) {
452 /* In case data is aligned, MurmurHash2 function */
453 while(len >= 4)
454 {
455 /* Mix 4 bytes at a time into the hash */
456 uint32_t k = *(uint32_t *)data; /* We don't care about the byte order */
457
458 _HASH_MIX(hash, k, m);
459
460 data += 4;
461 len -= 4;
462 }
463
464 /* Handle the last few bytes of the input */
465 switch(len) {
466 case 3: hash ^= data[2] << 16;
467 case 2: hash ^= data[1] << 8;
468 case 1: hash ^= data[0];
469 hash *= m;
470 }
471
472 } else {
473 /* Unaligned data, use alignment-safe slower version */
474
475 /* Pre-load the temp registers */
476 uint32_t t = 0, d = 0;
477 switch(align)
478 {
479 case 1: t |= data[2] << 16;
480 case 2: t |= data[1] << 8;
481 case 3: t |= data[0];
482 }
483 t <<= (8 * align);
484
485 data += 4-align;
486 len -= 4-align;
487
488 /* From this point, "data" can be read by chunks of 4 bytes */
489
490 int sl = 8 * (4-align);
491 int sr = 8 * align;
492
493 /* Mix */
494 while(len >= 4)
495 {
496 uint32_t k;
497
498 d = *(unsigned int *)data;
499 k = (t >> sr) | (d << sl);
500
501 _HASH_MIX(hash, k, m);
502
503 t = d;
504
505 data += 4;
506 len -= 4;
507 }
508
509 /* Handle leftover data in temp registers */
510 d = 0;
511 if(len >= align)
512 {
513 uint32_t k;
514
515 switch(align)
516 {
517 case 3: d |= data[2] << 16;
518 case 2: d |= data[1] << 8;
519 case 1: d |= data[0];
520 }
521
522 k = (t >> sr) | (d << sl);
523 _HASH_MIX(hash, k, m);
524
525 data += align;
526 len -= align;
527
528 /* Handle tail bytes */
529
530 switch(len)
531 {
532 case 3: hash ^= data[2] << 16;
533 case 2: hash ^= data[1] << 8;
534 case 1: hash ^= data[0];
535 hash *= m;
536 };
537 }
538 else
539 {
540 switch(len)
541 {
542 case 3: d |= data[2] << 16;
543 case 2: d |= data[1] << 8;
544 case 1: d |= data[0];
545 case 0: hash ^= (t >> sr) | (d << sl);
546 hash *= m;
547 }
548 }
549
550
551 }
552
553 /* Do a few final mixes of the hash to ensure the last few
554 bytes are well-incorporated. */
555 hash ^= hash >> 13;
556 hash *= m;
557 hash ^= hash >> 15;
558
559 return hash;
560}
561