Line data Source code
1 : /*
2 : * Copyright (C) 2017 Tim Rühsen
3 : * Copyright (C) 2016, 2017 Red Hat, Inc.
4 : *
5 : * Author: Nikos Mavrogiannopoulos
6 : *
7 : * This file is part of GnuTLS.
8 : *
9 : * The GnuTLS is free software; you can redistribute it and/or
10 : * modify it under the terms of the GNU Lesser General Public License
11 : * as published by the Free Software Foundation; either version 2.1 of
12 : * the License, or (at your option) any later version.
13 : *
14 : * This library is distributed in the hope that it will be useful, but
15 : * WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 : * Lesser General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU Lesser General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>
21 : *
22 : */
23 :
24 : #include "gnutls_int.h"
25 : #include "errors.h"
26 : #include "str.h"
27 : #include <unistr.h>
28 :
29 : #ifdef HAVE_LIBIDN2
30 :
31 : # include <idn2.h>
32 :
33 : # define ICAST char
34 :
35 : /**
36 : * gnutls_idna_map:
37 : * @input: contain the UTF-8 formatted domain name
38 : * @ilen: the length of the provided string
39 : * @out: the result in an null-terminated allocated string
40 : * @flags: should be zero
41 : *
42 : * This function will convert the provided UTF-8 domain name, to
43 : * its IDNA mapping in an allocated variable. Note that depending on the flags the used gnutls
44 : * library was compiled with, the output of this function may vary (i.e.,
45 : * may be IDNA2008, or IDNA2003).
46 : *
47 : * To force IDNA2008 specify the flag %GNUTLS_IDNA_FORCE_2008. In
48 : * the case GnuTLS is not compiled with the necessary dependencies,
49 : * %GNUTLS_E_UNIMPLEMENTED_FEATURE will be returned to indicate that
50 : * gnutls is unable to perform the requested conversion.
51 : *
52 : * Note also, that this function will return an empty string if an
53 : * empty string is provided as input.
54 : *
55 : * Returns: %GNUTLS_E_INVALID_UTF8_STRING on invalid UTF-8 data, or 0 on success.
56 : *
57 : * Since: 3.5.8
58 : **/
59 13129 : int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags)
60 : {
61 13129 : char *idna = NULL;
62 13129 : int rc, ret;
63 13129 : gnutls_datum_t istr;
64 13129 : unsigned int idn2_flags = IDN2_NFC_INPUT;
65 13129 : unsigned int idn2_tflags = IDN2_NFC_INPUT;
66 :
67 : /* IDN2_NONTRANSITIONAL automatically converts to lowercase
68 : * IDN2_NFC_INPUT converts to NFC before toASCII conversion
69 : *
70 : * Since IDN2_NONTRANSITIONAL implicitly does NFC conversion, we don't need
71 : * the additional IDN2_NFC_INPUT. But just for the unlikely case that the linked
72 : * library is not matching the headers when building and it doesn't support TR46,
73 : * we provide IDN2_NFC_INPUT.
74 : *
75 : * Without IDN2_USE_STD3_ASCII_RULES, the result could contain any ASCII characters,
76 : * e.g. 'evil.c\u2100.example.com' will be converted into
77 : * 'evil.ca/c.example.com', which seems no good idea. */
78 13129 : idn2_flags |= IDN2_NONTRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
79 13129 : idn2_tflags |= IDN2_TRANSITIONAL | IDN2_USE_STD3_ASCII_RULES;
80 :
81 13129 : if (ilen == 0) {
82 0 : out->data = (uint8_t*)gnutls_strdup("");
83 0 : out->size = 0;
84 0 : if (out->data == NULL)
85 0 : return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
86 : return 0;
87 : }
88 :
89 161463 : if (_gnutls_str_is_print(input, ilen)) {
90 13074 : return _gnutls_set_strdatum(out, input, ilen);
91 : }
92 :
93 55 : ret = _gnutls_set_strdatum(&istr, input, ilen);
94 55 : if (ret < 0) {
95 0 : gnutls_assert();
96 0 : return ret;
97 : }
98 :
99 55 : rc = idn2_to_ascii_8z((ICAST*)istr.data, (ICAST**)&idna, idn2_flags);
100 55 : if (rc == IDN2_DISALLOWED && !(flags & GNUTLS_IDNA_FORCE_2008))
101 1 : rc = idn2_to_ascii_8z((ICAST*)istr.data, (ICAST**)&idna, idn2_tflags);
102 :
103 55 : if (rc != IDN2_OK) {
104 4 : gnutls_assert();
105 4 : idna = NULL; /* in case idn2_lookup_u8 modifies &idna */
106 4 : _gnutls_debug_log("unable to convert name '%s' to IDNA format: %s\n", istr.data, idn2_strerror(rc));
107 4 : ret = GNUTLS_E_INVALID_UTF8_STRING;
108 4 : goto fail;
109 : }
110 :
111 51 : if (gnutls_free != idn2_free) {
112 51 : ret = _gnutls_set_strdatum(out, idna, strlen(idna));
113 : } else {
114 0 : out->data = (unsigned char*)idna;
115 0 : out->size = strlen(idna);
116 0 : idna = NULL;
117 0 : ret = 0;
118 : }
119 :
120 55 : fail:
121 55 : idn2_free(idna);
122 55 : gnutls_free(istr.data);
123 55 : return ret;
124 : }
125 :
126 : /**
127 : * gnutls_idna_reverse_map:
128 : * @input: contain the ACE (IDNA) formatted domain name
129 : * @ilen: the length of the provided string
130 : * @out: the result in an null-terminated allocated UTF-8 string
131 : * @flags: should be zero
132 : *
133 : * This function will convert an ACE (ASCII-encoded) domain name to a UTF-8 domain name.
134 : *
135 : * If GnuTLS is compiled without IDNA support, then this function
136 : * will return %GNUTLS_E_UNIMPLEMENTED_FEATURE.
137 : *
138 : * Note also, that this function will return an empty string if an
139 : * empty string is provided as input.
140 : *
141 : * Returns: A negative error code on error, or 0 on success.
142 : *
143 : * Since: 3.5.8
144 : **/
145 168 : int gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags)
146 : {
147 168 : char *u8 = NULL;
148 168 : int rc, ret;
149 168 : gnutls_datum_t istr;
150 :
151 168 : if (ilen == 0) {
152 0 : out->data = (uint8_t*)gnutls_strdup("");
153 0 : out->size = 0;
154 0 : if (out->data == NULL)
155 0 : return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
156 : return 0;
157 : }
158 :
159 168 : ret = _gnutls_set_strdatum(&istr, input, ilen);
160 168 : if (ret < 0) {
161 0 : gnutls_assert();
162 0 : return ret;
163 : }
164 :
165 : /* currently libidn2 just converts single labels, thus a wrapper function */
166 168 : rc = idn2_to_unicode_8z8z((char*)istr.data, &u8, 0);
167 168 : if (rc != IDN2_OK) {
168 40 : gnutls_assert();
169 40 : _gnutls_debug_log("unable to convert ACE name '%s' to UTF-8 format: %s\n", istr.data, idn2_strerror(rc));
170 40 : ret = GNUTLS_E_INVALID_UTF8_STRING;
171 40 : goto fail;
172 : }
173 :
174 128 : if (gnutls_malloc != malloc) {
175 0 : ret = _gnutls_set_strdatum(out, u8, strlen(u8));
176 : } else {
177 128 : out->data = (unsigned char*)u8;
178 128 : out->size = strlen(u8);
179 128 : u8 = NULL;
180 128 : ret = 0;
181 : }
182 168 : fail:
183 168 : idn2_free(u8);
184 168 : gnutls_free(istr.data);
185 168 : return ret;
186 : }
187 :
188 : #else /* no HAVE_LIBIDN2 */
189 :
190 : # undef gnutls_idna_map
191 : int gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags)
192 : {
193 : if (!_gnutls_str_is_print(input, ilen)) {
194 : return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
195 : }
196 :
197 : return _gnutls_set_strdatum(out, input, ilen);
198 : }
199 :
200 : int gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags)
201 : {
202 : return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
203 : }
204 : #endif /* HAVE_LIBIDN2 */
205 :
206 122 : int _gnutls_idna_email_map(const char *input, unsigned ilen, gnutls_datum_t *output)
207 : {
208 122 : const char *p = input;
209 :
210 669 : while(*p != 0 && *p != '@') {
211 548 : if (!c_isprint(*p))
212 1 : return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
213 547 : p++;
214 : }
215 :
216 1769 : if (_gnutls_str_is_print(input, ilen)) {
217 120 : return _gnutls_set_strdatum(output, input, ilen);
218 : }
219 :
220 1 : if (*p == '@') {
221 1 : unsigned name_part = p-input;
222 1 : int ret;
223 1 : gnutls_datum_t domain;
224 :
225 1 : ret = gnutls_idna_map(p+1, ilen-name_part-1, &domain, 0);
226 1 : if (ret < 0)
227 0 : return gnutls_assert_val(ret);
228 :
229 1 : output->data = gnutls_malloc(name_part+1+domain.size+1);
230 1 : if (output->data == NULL) {
231 0 : gnutls_free(domain.data);
232 0 : return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
233 : }
234 1 : memcpy(output->data, input, name_part);
235 1 : output->data[name_part] = '@';
236 1 : memcpy(&output->data[name_part+1], domain.data, domain.size);
237 1 : output->data[name_part+domain.size+1] = 0;
238 1 : output->size = name_part+domain.size+1;
239 1 : gnutls_free(domain.data);
240 1 : return 0;
241 : } else {
242 0 : return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
243 : }
244 : }
245 :
246 34 : int _gnutls_idna_email_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *output)
247 : {
248 34 : const char *p = input;
249 :
250 580 : while(*p != 0 && *p != '@') {
251 546 : if (!c_isprint(*p))
252 0 : return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
253 546 : p++;
254 : }
255 :
256 34 : if (*p == '@') {
257 12 : unsigned name_part = p-input;
258 12 : int ret;
259 12 : gnutls_datum_t domain;
260 :
261 12 : ret = gnutls_idna_reverse_map(p+1, ilen-name_part-1, &domain, 0);
262 12 : if (ret < 0)
263 8 : return gnutls_assert_val(ret);
264 :
265 4 : output->data = gnutls_malloc(name_part+1+domain.size+1);
266 4 : if (output->data == NULL) {
267 0 : gnutls_free(domain.data);
268 0 : return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
269 : }
270 4 : memcpy(output->data, input, name_part);
271 4 : output->data[name_part] = '@';
272 4 : memcpy(&output->data[name_part+1], domain.data, domain.size);
273 4 : output->data[name_part+domain.size+1] = 0;
274 4 : output->size = name_part+domain.size+1;
275 4 : gnutls_free(domain.data);
276 4 : return 0;
277 : } else {
278 22 : return gnutls_assert_val(GNUTLS_E_INVALID_UTF8_EMAIL);
279 : }
280 : }
|