/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 | } |