hash-format.c revision c971768955f826fb965d8ffbb13dac93c9bbead8
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen/* Copyright (c) 2010-2014 Dovecot authors, see the included COPYING file */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "lib.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "base64.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "hex-binary.h"
e074ffeaee1ce283bd42f167c6810e3d013f8218Timo Sirainen#include "str.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "hash-method.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "hash-format.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainenenum hash_encoding {
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen HASH_ENCODING_HEX,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen HASH_ENCODING_HEX_SHORT,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen HASH_ENCODING_BASE64
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstruct hash_format_list {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct hash_format_list *next;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const struct hash_method *method;
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen void *context;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen unsigned int bits;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen enum hash_encoding encoding;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen};
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainenstruct hash_format {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen pool_t pool;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const char *str;
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct hash_format_list *list, **pos;
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen unsigned char *digest;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainenhash_format_parse(const char *str, unsigned int *idxp,
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen const struct hash_method **method_r,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen unsigned int *bits_r, const char **error_r)
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen{
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen const char *name, *end, *bitsp;
8afe3f0e832b8b3483b692205bbd59c0110a20fdTimo Sirainen unsigned int bits, i = *idxp;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* we should have "hash_name}" or "hash_name:bits}" */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen end = strchr(str+i, '}');
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (end == NULL) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen *error_r = "Missing '}'";
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen return -1;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen }
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen *idxp = end - str;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen name = t_strdup_until(str+i, end);
4b8459c6c24b79d4ed5974ab6e3289a3f2b701c0Timo Sirainen
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen bitsp = strchr(name, ':');
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen if (bitsp != NULL)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen name = t_strdup_until(name, bitsp++);
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen *method_r = hash_method_lookup(name);
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen if (*method_r == NULL) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen *error_r = t_strconcat("Unknown hash method: ", name, NULL);
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen bits = (*method_r)->digest_size * 8;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen if (bitsp != NULL) {
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen if (str_to_uint(bitsp, &bits) < 0 ||
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen bits == 0 || bits > (*method_r)->digest_size*8) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen *error_r = t_strconcat("Invalid :bits number: ",
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen bitsp, NULL);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if ((bits % 8) != 0) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen *error_r = t_strconcat(
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen "Currently :bits must be divisible by 8: ",
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen bitsp, NULL);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen *bits_r = bits;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return 0;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen}
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainenstatic int
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainenhash_format_string_analyze(struct hash_format *format, const char *str,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const char **error_r)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct hash_format_list *list;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen unsigned int i;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen for (i = 0; str[i] != '\0'; i++) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (str[i] != '%')
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen continue;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen i++;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen list = p_new(format->pool, struct hash_format_list, 1);
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen list->encoding = HASH_ENCODING_HEX;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen *format->pos = list;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen format->pos = &list->next;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen if (str[i] == 'B') {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen list->encoding = HASH_ENCODING_BASE64;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen i++;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen } else if (str[i] == 'X') {
965e13eea8dc7f1da3769ab0c4667e36d0f33192Timo Sirainen list->encoding = HASH_ENCODING_HEX_SHORT;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen i++;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (str[i++] != '{') {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen *error_r = "No '{' after '%'";
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (hash_format_parse(str, &i, &list->method,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen &list->bits, error_r) < 0)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen list->context = p_malloc(format->pool,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen list->method->context_size);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen list->method->init(list->context);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen return 0;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen}
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenint hash_format_init(const char *format_string, struct hash_format **format_r,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen const char **error_r)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct hash_format *format;
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen pool_t pool;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen int ret;
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen pool = pool_alloconly_create("hash format", 1024);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen format = p_new(pool, struct hash_format, 1);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen format->pool = pool;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen format->str = p_strdup(pool, format_string);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen format->pos = &format->list;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen T_BEGIN {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen ret = hash_format_string_analyze(format, format_string,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen error_r);
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen if (ret < 0)
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen *error_r = p_strdup(format->pool, *error_r);
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen } T_END;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen if (ret < 0) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen *error_r = t_strdup(*error_r);
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen pool_unref(&pool);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen }
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen *format_r = format;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen return 0;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen}
f05b9dd37f830576ca7d32ec7071bf87906df3d2Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenvoid hash_format_loop(struct hash_format *format,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen const void *data, size_t size)
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen{
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen struct hash_format_list *list;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen for (list = format->list; list != NULL; list = list->next)
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen list->method->loop(list->context, data, size);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenvoid hash_format_reset(struct hash_format *format)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen struct hash_format_list *list;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen for (list = format->list; list != NULL; list = list->next) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen memset(list->context, 0, list->method->context_size);
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen list->method->init(list->context);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen}
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainenstatic void
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenhash_format_digest(string_t *dest, const struct hash_format_list *list,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const unsigned char *digest)
20a3870db4f78717574ee94bca1512994391b2abTimo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen unsigned int i, orig_len, size = list->bits / 8;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen i_assert(list->bits % 8 == 0);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen switch (list->encoding) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen case HASH_ENCODING_HEX:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen binary_to_hex_append(dest, digest, size);
20a3870db4f78717574ee94bca1512994391b2abTimo Sirainen break;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen case HASH_ENCODING_HEX_SHORT:
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen orig_len = str_len(dest);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen binary_to_hex_append(dest, digest, size);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* drop leading zeros, except if it's the only one */
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen for (i = orig_len; i < str_len(dest); i++) {
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (str_data(dest)[i] != '0')
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen break;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (i == str_len(dest)) i--;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen str_delete(dest, orig_len, i-orig_len);
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen break;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen case HASH_ENCODING_BASE64:
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen orig_len = str_len(dest);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen base64_encode(digest, size, dest);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* drop trailing '=' chars */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen while (str_len(dest) > orig_len &&
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen str_data(dest)[str_len(dest)-1] == '=')
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen str_truncate(dest, str_len(dest)-1);
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen break;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen }
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen}
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainenvoid hash_format_write(struct hash_format *format, string_t *dest)
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct hash_format_list *list;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen const char *p;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen unsigned int i, max_digest_size = 0;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
a912d084eb8024ce35462c9fd2d50b86b13d8d33Timo Sirainen for (list = format->list; list != NULL; list = list->next) {
a912d084eb8024ce35462c9fd2d50b86b13d8d33Timo Sirainen if (max_digest_size < list->method->digest_size)
a912d084eb8024ce35462c9fd2d50b86b13d8d33Timo Sirainen max_digest_size = list->method->digest_size;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen }
a912d084eb8024ce35462c9fd2d50b86b13d8d33Timo Sirainen if (format->digest == NULL)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen format->digest = p_malloc(format->pool, max_digest_size);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen list = format->list;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen for (i = 0; format->str[i] != '\0'; i++) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (format->str[i] != '%') {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen 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);
}