1 /*
2  * Copyright (c) Edward Thomson.  All rights reserved.
3  *
4  * This file is part of ntlmclient, distributed under the MIT license.
5  * For full terms and copyright information, and for third-party
6  * copyright information, see the included LICENSE.txt file.
7  */
8 
9 #include <locale.h>
10 #include <iconv.h>
11 #include <string.h>
12 #include <errno.h>
13 
14 #include "ntlmclient.h"
15 #include "unicode.h"
16 #include "ntlm.h"
17 #include "compat.h"
18 
19 typedef enum {
20 	unicode_iconv_utf8_to_16,
21 	unicode_iconv_utf16_to_8
22 } unicode_iconv_encoding_direction;
23 
ntlm_unicode_init(ntlm_client * ntlm)24 bool ntlm_unicode_init(ntlm_client *ntlm)
25 {
26 	ntlm->unicode_ctx.utf8_to_16 = iconv_open("UTF-16LE", "UTF-8");
27 	ntlm->unicode_ctx.utf16_to_8 = iconv_open("UTF-8", "UTF-16LE");
28 
29 	if (ntlm->unicode_ctx.utf8_to_16 == (iconv_t)-1 ||
30 	    ntlm->unicode_ctx.utf16_to_8 == (iconv_t)-1) {
31 		if (errno == EINVAL)
32 			ntlm_client_set_errmsg(ntlm,
33 				"iconv does not support UTF8 <-> UTF16 conversion");
34 		else
35 			ntlm_client_set_errmsg(ntlm, strerror(errno));
36 
37 		return false;
38 	}
39 
40 	return true;
41 }
42 
unicode_iconv_encoding_convert(char ** converted,size_t * converted_len,ntlm_client * ntlm,const char * string,size_t string_len,unicode_iconv_encoding_direction direction)43 static inline bool unicode_iconv_encoding_convert(
44 	char **converted,
45 	size_t *converted_len,
46 	ntlm_client *ntlm,
47 	const char *string,
48 	size_t string_len,
49 	unicode_iconv_encoding_direction direction)
50 {
51 	char *in_start, *out_start, *out, *new_out;
52 	size_t in_start_len, out_start_len, out_size, nul_size, ret, written = 0;
53 	iconv_t converter;
54 
55 	*converted = NULL;
56 	*converted_len = 0;
57 
58 	/*
59 	 * When translating UTF8 to UTF16, these strings are only used
60 	 * internally, and we obey the given length, so we can simply
61 	 * use a buffer that is 2x the size.  When translating from UTF16
62 	 * to UTF8, we may need to return to callers, so we need to NUL
63 	 * terminate and expect an extra byte for UTF8, two for UTF16.
64 	 */
65 	if (direction == unicode_iconv_utf8_to_16) {
66 		converter = ntlm->unicode_ctx.utf8_to_16;
67 		out_size = (string_len * 2) + 2;
68 		nul_size = 2;
69 	} else {
70 		converter = ntlm->unicode_ctx.utf16_to_8;
71 		out_size = (string_len / 2) + 1;
72 		nul_size = 1;
73 	}
74 
75 	/* Round to the nearest multiple of 8 */
76 	out_size = (out_size + 7) & ~7;
77 
78 	if ((out = malloc(out_size)) == NULL) {
79 		ntlm_client_set_errmsg(ntlm, "out of memory");
80 		return false;
81 	}
82 
83 	in_start = (char *)string;
84 	in_start_len = string_len;
85 
86 	while (true) {
87 		out_start = out + written;
88 		out_start_len = (out_size - nul_size) - written;
89 
90 		ret = iconv(converter, &in_start, &in_start_len, &out_start, &out_start_len);
91 		written = (out_size - nul_size) - out_start_len;
92 
93 		if (ret == 0)
94 			break;
95 
96 		if (ret == (size_t)-1 && errno != E2BIG) {
97 			ntlm_client_set_errmsg(ntlm, strerror(errno));
98 			goto on_error;
99 		}
100 
101 		/* Grow buffer size by 1.5 (rounded up to a multiple of 8) */
102 		out_size = ((((out_size << 1) - (out_size >> 1)) + 7) & ~7);
103 
104 		if (out_size > NTLM_UNICODE_MAX_LEN) {
105 			ntlm_client_set_errmsg(ntlm, "unicode conversion too large");
106 			goto on_error;
107 		}
108 
109 		if ((new_out = realloc(out, out_size)) == NULL) {
110 			ntlm_client_set_errmsg(ntlm, "out of memory");
111 			goto on_error;
112 		}
113 
114 		out = new_out;
115 	}
116 
117 	if (in_start_len != 0) {
118 		ntlm_client_set_errmsg(ntlm,
119 			"invalid unicode string; trailing data remains");
120 		goto on_error;
121 	}
122 
123 	/* NUL terminate */
124 	out[written] = '\0';
125 
126 	if (direction == unicode_iconv_utf8_to_16)
127 		out[written + 1] = '\0';
128 
129 	*converted = out;
130 
131 	if (converted_len)
132 		*converted_len = written;
133 
134 	return true;
135 
136 on_error:
137 	free(out);
138 	return false;
139 }
140 
ntlm_unicode_utf8_to_16(char ** converted,size_t * converted_len,ntlm_client * ntlm,const char * string,size_t string_len)141 bool ntlm_unicode_utf8_to_16(
142 	char **converted,
143 	size_t *converted_len,
144 	ntlm_client *ntlm,
145 	const char *string,
146 	size_t string_len)
147 {
148 	return unicode_iconv_encoding_convert(
149 		converted, converted_len, ntlm, string, string_len,
150 		unicode_iconv_utf8_to_16);
151 }
152 
ntlm_unicode_utf16_to_8(char ** converted,size_t * converted_len,ntlm_client * ntlm,const char * string,size_t string_len)153 bool ntlm_unicode_utf16_to_8(
154 	char **converted,
155 	size_t *converted_len,
156 	ntlm_client *ntlm,
157 	const char *string,
158 	size_t string_len)
159 {
160 	return unicode_iconv_encoding_convert(
161 		converted, converted_len, ntlm, string, string_len,
162 		unicode_iconv_utf16_to_8);
163 }
164 
ntlm_unicode_shutdown(ntlm_client * ntlm)165 void ntlm_unicode_shutdown(ntlm_client *ntlm)
166 {
167 	if (ntlm->unicode_ctx.utf16_to_8 != (iconv_t)0 &&
168 	    ntlm->unicode_ctx.utf16_to_8 != (iconv_t)-1)
169 		iconv_close(ntlm->unicode_ctx.utf16_to_8);
170 
171 	if (ntlm->unicode_ctx.utf8_to_16 != (iconv_t)0 &&
172 	    ntlm->unicode_ctx.utf8_to_16 != (iconv_t)-1)
173 		iconv_close(ntlm->unicode_ctx.utf8_to_16);
174 
175 	ntlm->unicode_ctx.utf8_to_16 = (iconv_t)-1;
176 	ntlm->unicode_ctx.utf16_to_8 = (iconv_t)-1;
177 }
178