/* * Copyright (c) Edward Thomson. All rights reserved. * * This file is part of ntlmclient, distributed under the MIT license. * For full terms and copyright information, and for third-party * copyright information, see the included LICENSE.txt file. */ #include #include #include #include #include "ntlmclient.h" #include "unicode.h" #include "ntlm.h" #include "compat.h" struct ntlm_unicode_ctx { ntlm_client *ntlm; iconv_t utf8_to_16; iconv_t utf16_to_8; }; ntlm_unicode_ctx *ntlm_unicode_ctx_init(ntlm_client *ntlm) { ntlm_unicode_ctx *ctx; if ((ctx = calloc(1, sizeof(ntlm_unicode_ctx))) == NULL) return NULL; ctx->ntlm = ntlm; ctx->utf8_to_16 = (iconv_t)-1; ctx->utf16_to_8 = (iconv_t)-1; return ctx; } typedef enum { unicode_iconv_utf8_to_16, unicode_iconv_utf16_to_8 } unicode_iconv_encoding_direction; static inline bool unicode_iconv_init(ntlm_unicode_ctx *ctx) { if (ctx->utf8_to_16 != (iconv_t)-1 || ctx->utf16_to_8 != (iconv_t)-1) return true; if ((ctx->utf8_to_16 = iconv_open("UTF-16LE", "UTF-8")) == (iconv_t)-1 || (ctx->utf16_to_8 = iconv_open("UTF-8", "UTF-16LE")) == (iconv_t)-1) { if (errno == EINVAL) ntlm_client_set_errmsg(ctx->ntlm, "iconv does not support UTF8 <-> UTF16 conversion"); else ntlm_client_set_errmsg(ctx->ntlm, strerror(errno)); return false; } return true; } static inline bool unicode_iconv_encoding_convert( char **converted, size_t *converted_len, ntlm_unicode_ctx *ctx, const char *string, size_t string_len, unicode_iconv_encoding_direction direction) { char *in_start, *out_start, *out, *new_out; size_t in_start_len, out_start_len, out_size, nul_size, ret, written = 0; iconv_t converter; *converted = NULL; *converted_len = 0; if (!unicode_iconv_init(ctx)) return false; /* * When translating UTF8 to UTF16, these strings are only used * internally, and we obey the given length, so we can simply * use a buffer that is 2x the size. When translating from UTF16 * to UTF8, we may need to return to callers, so we need to NUL * terminate and expect an extra byte for UTF8, two for UTF16. */ if (direction == unicode_iconv_utf8_to_16) { converter = ctx->utf8_to_16; out_size = (string_len * 2) + 2; nul_size = 2; } else { converter = ctx->utf16_to_8; out_size = (string_len / 2) + 1; nul_size = 1; } /* Round to the nearest multiple of 8 */ out_size = (out_size + 7) & ~7; if ((out = malloc(out_size)) == NULL) { ntlm_client_set_errmsg(ctx->ntlm, "out of memory"); return false; } in_start = (char *)string; in_start_len = string_len; while (true) { out_start = out + written; out_start_len = (out_size - nul_size) - written; ret = iconv(converter, &in_start, &in_start_len, &out_start, &out_start_len); written = (out_size - nul_size) - out_start_len; if (ret == 0) break; if (ret == (size_t)-1 && errno != E2BIG) { ntlm_client_set_errmsg(ctx->ntlm, strerror(errno)); goto on_error; } /* Grow buffer size by 1.5 (rounded up to a multiple of 8) */ out_size = ((((out_size << 1) - (out_size >> 1)) + 7) & ~7); if (out_size > NTLM_UNICODE_MAX_LEN) { ntlm_client_set_errmsg(ctx->ntlm, "unicode conversion too large"); goto on_error; } if ((new_out = realloc(out, out_size)) == NULL) { ntlm_client_set_errmsg(ctx->ntlm, "out of memory"); goto on_error; } out = new_out; } if (in_start_len != 0) { ntlm_client_set_errmsg(ctx->ntlm, "invalid unicode string; trailing data remains"); goto on_error; } /* NUL terminate */ out[written] = '\0'; if (direction == unicode_iconv_utf8_to_16) out[written + 1] = '\0'; *converted = out; if (converted_len) *converted_len = written; return true; on_error: free(out); return false; } bool ntlm_unicode_utf8_to_16( char **converted, size_t *converted_len, ntlm_unicode_ctx *ctx, const char *string, size_t string_len) { return unicode_iconv_encoding_convert( converted, converted_len, ctx, string, string_len, unicode_iconv_utf8_to_16); } bool ntlm_unicode_utf16_to_8( char **converted, size_t *converted_len, ntlm_unicode_ctx *ctx, const char *string, size_t string_len) { return unicode_iconv_encoding_convert( converted, converted_len, ctx, string, string_len, unicode_iconv_utf16_to_8); } void ntlm_unicode_ctx_free(ntlm_unicode_ctx *ctx) { if (!ctx) return; if (ctx->utf16_to_8 != (iconv_t)-1) iconv_close(ctx->utf16_to_8); if (ctx->utf8_to_16 != (iconv_t)-1) iconv_close(ctx->utf8_to_16); free(ctx); }