summaryrefslogtreecommitdiffstats
path: root/src/lib/hash-format.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hash-format.c')
-rw-r--r--src/lib/hash-format.c245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/lib/hash-format.c b/src/lib/hash-format.c
new file mode 100644
index 0000000..41ed495
--- /dev/null
+++ b/src/lib/hash-format.c
@@ -0,0 +1,245 @@
+/* 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);
+}