17f62296c3add9a07273c2362aeab2da649d7e08
[freeradius.git] / src / modules / rlm_idn / rlm_idn.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_idn.c
20  * @brief Internationalized Domain Name encoding for DNS aka IDNA aka RFC3490
21  *
22  * @copyright 2013  Brian S. Julin <bjulin@clarku.edu>
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28
29 #include <idna.h>
30
31 /*
32  *      Structure for module configuration
33  */
34 typedef struct rlm_idn_t {
35         char const      *xlat_name;
36         bool            use_std3_ascii_rules;
37         bool            allow_unassigned;
38 } rlm_idn_t;
39
40 /*
41  *      The primary use case for this module is DNS-safe encoding of realms
42  *      appearing in requests for a DDDS scheme.  Some notes on that usage
43  *      scenario:
44  *
45  *      RFC2865 5.1 User-Name may be one of:
46  *
47  *      1) UTF-8 text: in which case this conversion is needed
48  *
49  *      2) realm part of an NAI: in which case this conversion should do nothing
50  *         since only ASCII digits, ASCII alphas, ASCII dots, and ASCII hyphens
51  *         are allowed.
52  *
53  *      3) "A name in ASN.1 form used in Public Key authentication systems.":
54  *         I count four things in that phrase that are rather ... vague.
55  *         However, most X.509 docs yell at you to IDNA internationalized
56  *         domain names to IA5String, so if it is coming from inside an X.509
57  *         certificate IDNA should be idempotent in the encode direction.
58  *
59  *         Except for that last loophole, which we will leave up to the user
60  *         to sort out, we should be safe in processing the realm as UTF-8.
61  */
62
63
64 /*
65  *      A mapping of configuration file names to internal variables.
66  */
67 static const CONF_PARSER mod_config[] = {
68         /*
69          *      If a STRINGPREP profile other than NAMEPREP is ever desired,
70          *      we can implement an option, and it will default to NAMEPREP settings.
71          *      ...and if we want raw punycode or to tweak Bootstring parameters,
72          *      we can do similar things.  All defaults should result in IDNA
73          *      ToASCII with the use_std3_ascii_rules flag set, allow_unassigned unset,
74          *      because that is the forseeable use case.
75          *
76          *      Note that doing anything much different will require choosing the
77          *      appropriate libidn API functions, as we currently call the IDNA
78          *      convenience functions.
79          *
80          *      Also note that right now we do not provide ToUnicode, which may or
81          *      may not be useful as an xlat... depends on how the results need to
82          *      be used.
83          */
84
85         { "allow_unassigned", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_idn_t, allow_unassigned), "no" },
86         { "use_std3_ascii_rules", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_idn_t, use_std3_ascii_rules), "yes" },
87
88         { NULL, -1, 0, NULL, NULL }
89 };
90
91 static ssize_t xlat_idna(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
92 {
93         rlm_idn_t *inst = instance;
94         char *idna = NULL;
95         int res;
96         size_t len;
97         int flags = 0;
98
99         if (inst->use_std3_ascii_rules) {
100                 flags |= IDNA_USE_STD3_ASCII_RULES;
101         }
102         if (inst->allow_unassigned) {
103                 flags |= IDNA_ALLOW_UNASSIGNED;
104         }
105
106         res = idna_to_ascii_8z(fmt, &idna, flags);
107         if (res) {
108                 if (idna) {
109                         free (idna); /* Docs unclear, be safe. */
110                 }
111
112                 REDEBUG("%s", idna_strerror(res));
113                 return -1;
114         }
115
116         len = strlen(idna);
117
118         /* 253 is max DNS length */
119         if (!((len < (freespace - 1)) && (len <= 253))) {
120                 /* Never provide a truncated result, as it may be queried. */
121                 REDEBUG("Conversion was truncated");
122
123                 free(idna);
124                 return -1;
125
126         }
127
128         strlcpy(out, idna, freespace);
129         free(idna);
130
131         return len;
132 }
133
134 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
135 {
136         rlm_idn_t *inst = instance;
137         char const *xlat_name;
138
139         xlat_name = cf_section_name2(conf);
140         if (!xlat_name) {
141                 xlat_name = cf_section_name1(conf);
142         }
143
144         inst->xlat_name = xlat_name;
145
146         xlat_register(inst->xlat_name, xlat_idna, NULL, inst);
147
148         return 0;
149 }
150
151 extern module_t rlm_idn;
152 module_t rlm_idn = {
153         .magic          = RLM_MODULE_INIT,
154         .name           = "idn",
155         .type           = RLM_TYPE_THREAD_SAFE,
156         .inst_size      = sizeof(rlm_idn_t),
157         .config         = mod_config,
158         .bootstrap      = mod_bootstrap
159 };