Navigation


source: freeDiameter/libfdproto/ostr.c

Last change on this file was 1127:1af09cc156d6, checked in by Sebastien Decugis <sdecugis@freediameter.net>, 8 years ago

Updated copyright information

File size: 15.7 KB
Line 
1/*********************************************************************************************************
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
Note: See TracBrowser for help on using the repository browser.