/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "base64.h" #include "hex-binary.h" #include "str.h" #include "hash-method.h" #include "hash-format.h" enum hash_encoding { HASH_ENCODING_HEX, HASH_ENCODING_HEX_SHORT, HASH_ENCODING_BASE64 }; struct hash_format_list { struct hash_format_list *next; const struct hash_method *method; void *context; unsigned int bits; enum hash_encoding encoding; }; struct hash_format { pool_t pool; const char *str; struct hash_format_list *list, **pos; unsigned char *digest; }; static int hash_format_parse(const char *str, unsigned int *idxp, const struct hash_method **method_r, unsigned int *bits_r, const char **error_r) { const char *name, *end, *bitsp; unsigned int bits, i = *idxp; /* we should have "hash_name}" or "hash_name:bits}" */ end = strchr(str+i, '}'); if (end == NULL) { *error_r = "Missing '}'"; return -1; } *idxp = end - str; name = t_strdup_until(str+i, end); bitsp = strchr(name, ':'); if (bitsp != NULL) name = t_strdup_until(name, bitsp++); *method_r = hash_method_lookup(name); if (*method_r == NULL) { *error_r = t_strconcat("Unknown hash method: ", name, NULL); return -1; } bits = (*method_r)->digest_size * 8; if (bitsp != NULL) { if (str_to_uint(bitsp, &bits) < 0 || bits == 0 || bits > (*method_r)->digest_size*8) { *error_r = t_strconcat("Invalid :bits number: ", bitsp, NULL); return -1; } if ((bits % 8) != 0) { *error_r = t_strconcat( "Currently :bits must be divisible by 8: ", bitsp, NULL); return -1; } } *bits_r = bits; return 0; } static int hash_format_string_analyze(struct hash_format *format, const char *str, const char **error_r) { struct hash_format_list *list; unsigned int i; for (i = 0; str[i] != '\0'; i++) { if (str[i] != '%') continue; i++; list = p_new(format->pool, struct hash_format_list, 1); list->encoding = HASH_ENCODING_HEX; *format->pos = list; format->pos = &list->next; if (str[i] == 'B') { list->encoding = HASH_ENCODING_BASE64; i++; } else if (str[i] == 'X') { list->encoding = HASH_ENCODING_HEX_SHORT; i++; } if (str[i++] != '{') { *error_r = "No '{' after '%'"; return -1; } if (hash_format_parse(str, &i, &list->method, &list->bits, error_r) < 0) return -1; list->context = p_malloc(format->pool, list->method->context_size); list->method->init(list->context); } return 0; } int hash_format_init(const char *format_string, struct hash_format **format_r, const char **error_r) { struct hash_format *format; pool_t pool; int ret; pool = pool_alloconly_create("hash format", 1024); format = p_new(pool, struct hash_format, 1); format->pool = pool; format->str = p_strdup(pool, format_string); format->pos = &format->list; T_BEGIN { ret = hash_format_string_analyze(format, format_string, error_r); if (ret < 0) *error_r = p_strdup(format->pool, *error_r); } T_END; if (ret < 0) { *error_r = t_strdup(*error_r); pool_unref(&pool); return -1; } *format_r = format; return 0; } void hash_format_loop(struct hash_format *format, const void *data, size_t size) { struct hash_format_list *list; for (list = format->list; list != NULL; list = list->next) list->method->loop(list->context, data, size); } void hash_format_reset(struct hash_format *format) { struct hash_format_list *list; for (list = format->list; list != NULL; list = list->next) { memset(list->context, 0, list->method->context_size); list->method->init(list->context); } } static void hash_format_digest(string_t *dest, const struct hash_format_list *list, const unsigned char *digest) { unsigned int i, orig_len, size = list->bits / 8; i_assert(list->bits % 8 == 0); switch (list->encoding) { case HASH_ENCODING_HEX: binary_to_hex_append(dest, digest, size); break; case HASH_ENCODING_HEX_SHORT: orig_len = str_len(dest); binary_to_hex_append(dest, digest, size); /* drop leading zeros, except if it's the only one */ for (i = orig_len; i < str_len(dest); i++) { if (str_data(dest)[i] != '0') break; } if (i == str_len(dest)) i--; str_delete(dest, orig_len, i-orig_len); break; case HASH_ENCODING_BASE64: orig_len = str_len(dest); base64_encode(digest, size, dest); /* drop trailing '=' chars */ while (str_len(dest) > orig_len && str_data(dest)[str_len(dest)-1] == '=') str_truncate(dest, str_len(dest)-1); break; } } void hash_format_write(struct hash_format *format, string_t *dest) { struct hash_format_list *list; const char *p; unsigned int i, max_digest_size = 0; for (list = format->list; list != NULL; list = list->next) { if (max_digest_size < list->method->digest_size) max_digest_size = list->method->digest_size; } if (format->digest == NULL) format->digest = p_malloc(format->pool, max_digest_size); list = format->list; for (i = 0; format->str[i] != '\0'; i++) { if (format->str[i] != '%') { str_append_c(dest, format->str[i]); continue; } /* we already verified that the string is ok */ i_assert(list != NULL); list->method->result(list->context, format->digest); hash_format_digest(dest, list, format->digest); list = list->next; p = strchr(format->str+i, '}'); i_assert(p != NULL); i = p - format->str; } } void hash_format_deinit(struct hash_format **_format, string_t *dest) { struct hash_format *format = *_format; *_format = NULL; hash_format_write(format, dest); pool_unref(&format->pool); } void hash_format_deinit_free(struct hash_format **_format) { struct hash_format *format = *_format; *_format = NULL; pool_unref(&format->pool); }