Coverage Report

Created: 2026-07-30 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/hash-format.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "base64.h"
5
#include "hex-binary.h"
6
#include "str.h"
7
#include "hash-method.h"
8
#include "hash-format.h"
9
10
enum hash_encoding {
11
  HASH_ENCODING_HEX,
12
  HASH_ENCODING_HEX_SHORT,
13
  HASH_ENCODING_BASE64
14
};
15
16
struct hash_format_list {
17
  struct hash_format_list *next;
18
19
  const struct hash_method *method;
20
  void *context;
21
  unsigned int bits;
22
  enum hash_encoding encoding;
23
};
24
25
struct hash_format {
26
  pool_t pool;
27
  const char *str;
28
29
  struct hash_format_list *list, **pos;
30
  unsigned char *digest;
31
};
32
33
static int
34
hash_format_parse(const char *str, unsigned int *idxp,
35
      const struct hash_method **method_r,
36
      unsigned int *bits_r, const char **error_r)
37
0
{
38
0
  const char *name, *end, *bitsp;
39
0
  unsigned int bits, i = *idxp;
40
41
  /* we should have "hash_name}" or "hash_name:bits}" */
42
0
  end = strchr(str+i, '}');
43
0
  if (end == NULL) {
44
0
    *error_r = "Missing '}'";
45
0
    return -1;
46
0
  }
47
0
  *idxp = end - str;
48
0
  name = t_strdup_until(str+i, end);
49
50
0
  bitsp = strchr(name, ':');
51
0
  if (bitsp != NULL)
52
0
    name = t_strdup_until(name, bitsp++);
53
54
0
  *method_r = hash_method_lookup(name);
55
0
  if (*method_r == NULL) {
56
0
    *error_r = t_strconcat("Unknown hash method: ", name, NULL);
57
0
    return -1;
58
0
  }
59
60
0
  bits = (*method_r)->digest_size * 8;
61
0
  if (bitsp != NULL) {
62
0
    if (str_to_uint(bitsp, &bits) < 0 ||
63
0
        bits == 0 || bits > (*method_r)->digest_size*8) {
64
0
      *error_r = t_strconcat("Invalid :bits number: ",
65
0
                 bitsp, NULL);
66
0
      return -1;
67
0
    }
68
0
    if ((bits % 8) != 0) {
69
0
      *error_r = t_strconcat(
70
0
        "Currently :bits must be divisible by 8: ",
71
0
        bitsp, NULL);
72
0
      return -1;
73
0
    }
74
0
  }
75
0
  *bits_r = bits;
76
0
  return 0;
77
0
}
78
79
static int
80
hash_format_string_analyze(struct hash_format *format, const char *str,
81
         const char **error_r)
82
0
{
83
0
  struct hash_format_list *list;
84
0
  unsigned int i;
85
86
0
  for (i = 0; str[i] != '\0'; i++) {
87
0
    if (str[i] != '%')
88
0
      continue;
89
0
    i++;
90
91
0
    list = p_new(format->pool, struct hash_format_list, 1);
92
0
    list->encoding = HASH_ENCODING_HEX;
93
0
    *format->pos = list;
94
0
    format->pos = &list->next;
95
96
0
    if (str[i] == 'B') {
97
0
      list->encoding = HASH_ENCODING_BASE64;
98
0
      i++;
99
0
    } else if (str[i] == 'X') {
100
0
      list->encoding = HASH_ENCODING_HEX_SHORT;
101
0
      i++;
102
0
    }
103
0
    if (str[i++] != '{') {
104
0
      *error_r = "No '{' after '%'";
105
0
      return -1;
106
0
    }
107
0
    if (hash_format_parse(str, &i, &list->method,
108
0
              &list->bits, error_r) < 0)
109
0
      return -1;
110
0
    list->context = p_malloc(format->pool,
111
0
           list->method->context_size);
112
0
    list->method->init(list->context);
113
0
  }
114
0
  return 0;
115
0
}
116
117
int hash_format_init(const char *format_string, struct hash_format **format_r,
118
         const char **error_r)
119
0
{
120
0
  struct hash_format *format;
121
0
  pool_t pool;
122
0
  int ret;
123
124
0
  pool = pool_alloconly_create("hash format", 1024);
125
0
  format = p_new(pool, struct hash_format, 1);
126
0
  format->pool = pool;
127
0
  format->str = p_strdup(pool, format_string);
128
0
  format->pos = &format->list;
129
0
  T_BEGIN {
130
0
    ret = hash_format_string_analyze(format, format_string,
131
0
             error_r);
132
0
    if (ret < 0)
133
0
      *error_r = p_strdup(format->pool, *error_r);
134
0
  } T_END;
135
0
  if (ret < 0) {
136
0
    *error_r = t_strdup(*error_r);
137
0
    pool_unref(&pool);
138
0
    return -1;
139
0
  }
140
0
  *format_r = format;
141
0
  return 0;
142
0
}
143
144
void hash_format_loop(struct hash_format *format,
145
          const void *data, size_t size)
146
0
{
147
0
  struct hash_format_list *list;
148
149
0
  for (list = format->list; list != NULL; list = list->next)
150
0
    list->method->loop(list->context, data, size);
151
0
}
152
153
void hash_format_reset(struct hash_format *format)
154
0
{
155
0
  struct hash_format_list *list;
156
157
0
  for (list = format->list; list != NULL; list = list->next) {
158
0
    memset(list->context, 0, list->method->context_size);
159
0
    list->method->init(list->context);
160
0
  }
161
0
}
162
163
static void
164
hash_format_digest(string_t *dest, const struct hash_format_list *list,
165
       const unsigned char *digest)
166
0
{
167
0
  unsigned int i, orig_len, size = list->bits / 8;
168
169
0
  i_assert(list->bits % 8 == 0);
170
171
0
  switch (list->encoding) {
172
0
  case HASH_ENCODING_HEX:
173
0
    binary_to_hex_append(dest, digest, size);
174
0
    break;
175
0
  case HASH_ENCODING_HEX_SHORT:
176
0
    orig_len = str_len(dest);
177
0
    binary_to_hex_append(dest, digest, size);
178
    /* drop leading zeros, except if it's the only one */
179
0
    for (i = orig_len; i < str_len(dest); i++) {
180
0
      if (str_data(dest)[i] != '0')
181
0
        break;
182
0
    }
183
0
    if (i == str_len(dest)) i--;
184
0
    str_delete(dest, orig_len, i-orig_len);
185
0
    break;
186
0
  case HASH_ENCODING_BASE64:
187
0
    orig_len = str_len(dest);
188
0
    base64_encode(digest, size, dest);
189
    /* drop trailing '=' chars */
190
0
    while (str_len(dest) > orig_len &&
191
0
           str_data(dest)[str_len(dest)-1] == '=')
192
0
      str_truncate(dest, str_len(dest)-1);
193
0
    break;
194
0
  }
195
0
}
196
197
void hash_format_write(struct hash_format *format, string_t *dest)
198
0
{
199
0
  struct hash_format_list *list;
200
0
  const char *p;
201
0
  unsigned int i, max_digest_size = 0;
202
203
0
  for (list = format->list; list != NULL; list = list->next) {
204
0
    if (max_digest_size < list->method->digest_size)
205
0
      max_digest_size = list->method->digest_size;
206
0
  }
207
0
  if (format->digest == NULL)
208
0
    format->digest = p_malloc(format->pool, max_digest_size);
209
210
0
  list = format->list;
211
0
  for (i = 0; format->str[i] != '\0'; i++) {
212
0
    if (format->str[i] != '%') {
213
0
      str_append_c(dest, format->str[i]);
214
0
      continue;
215
0
    }
216
217
    /* we already verified that the string is ok */
218
0
    i_assert(list != NULL);
219
0
    list->method->result(list->context, format->digest);
220
0
    hash_format_digest(dest, list, format->digest);
221
0
    list = list->next;
222
223
0
    p = strchr(format->str+i, '}');
224
0
    i_assert(p != NULL);
225
0
    i = p - format->str;
226
0
  }
227
0
}
228
229
void hash_format_deinit(struct hash_format **_format, string_t *dest)
230
0
{
231
0
  struct hash_format *format = *_format;
232
233
0
  *_format = NULL;
234
235
0
  hash_format_write(format, dest);
236
0
  pool_unref(&format->pool);
237
0
}
238
239
void hash_format_deinit_free(struct hash_format **_format)
240
0
{
241
0
  struct hash_format *format = *_format;
242
243
0
  *_format = NULL;
244
0
  pool_unref(&format->pool);
245
0
}