chiark / gitweb /
Prep v236 : Add missing SPDX-License-Identifier (2/9) src/basic
[elogind.git] / src / basic / khash.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2016 Lennart Poettering
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <linux/if_alg.h>
22 #include <stdbool.h>
23 #include <sys/socket.h>
24
25 #include "alloc-util.h"
26 #include "fd-util.h"
27 #include "hexdecoct.h"
28 #include "khash.h"
29 #include "macro.h"
30 #include "missing.h"
31 #include "string-util.h"
32 #include "util.h"
33
34 /* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but
35  * let's add some extra room, the few wasted bytes don't really matter... */
36 #define LONGEST_DIGEST 128
37
38 struct khash {
39         int fd;
40         char *algorithm;
41         uint8_t digest[LONGEST_DIGEST+1];
42         size_t digest_size;
43         bool digest_valid;
44 };
45
46 int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
47         union {
48                 struct sockaddr sa;
49                 struct sockaddr_alg alg;
50         } sa = {
51                 .alg.salg_family = AF_ALG,
52                 .alg.salg_type = "hash",
53         };
54
55         _cleanup_(khash_unrefp) khash *h = NULL;
56         _cleanup_close_ int fd = -1;
57         ssize_t n;
58
59         assert(ret);
60         assert(key || key_size == 0);
61
62         /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
63         if (isempty(algorithm))
64                 return -EINVAL;
65
66         /* Overly long hash algorithm names we definitely do not support */
67         if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
68                 return -EOPNOTSUPP;
69
70         fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
71         if (fd < 0)
72                 return -errno;
73
74         strcpy((char*) sa.alg.salg_name, algorithm);
75         if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
76                 if (errno == ENOENT)
77                         return -EOPNOTSUPP;
78                 return -errno;
79         }
80
81         if (key) {
82                 if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
83                         return -errno;
84         }
85
86         h = new0(khash, 1);
87         if (!h)
88                 return -ENOMEM;
89
90         h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
91         if (h->fd < 0)
92                 return -errno;
93
94         h->algorithm = strdup(algorithm);
95         if (!h->algorithm)
96                 return -ENOMEM;
97
98         /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
99         (void) send(h->fd, NULL, 0, 0);
100
101         /* Figure out the digest size */
102         n = recv(h->fd, h->digest, sizeof(h->digest), 0);
103         if (n < 0)
104                 return -errno;
105         if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
106                 return -EOPNOTSUPP;
107
108         h->digest_size = (size_t) n;
109         h->digest_valid = true;
110
111         /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
112         (void) send(h->fd, NULL, 0, 0);
113
114         *ret = h;
115         h = NULL;
116
117         return 0;
118 }
119
120 int khash_new(khash **ret, const char *algorithm) {
121         return khash_new_with_key(ret, algorithm, NULL, 0);
122 }
123
124 khash* khash_unref(khash *h) {
125         if (!h)
126                 return NULL;
127
128         safe_close(h->fd);
129         free(h->algorithm);
130         return mfree(h);
131 }
132
133 int khash_dup(khash *h, khash **ret) {
134         _cleanup_(khash_unrefp) khash *copy = NULL;
135
136         assert(h);
137         assert(ret);
138
139         copy = newdup(khash, h, 1);
140         if (!copy)
141                 return -ENOMEM;
142
143         copy->fd = -1;
144         copy->algorithm = strdup(h->algorithm);
145         if (!copy->algorithm)
146                 return -ENOMEM;
147
148         copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
149         if (copy->fd < 0)
150                 return -errno;
151
152         *ret = copy;
153         copy = NULL;
154
155         return 0;
156 }
157
158 const char *khash_get_algorithm(khash *h) {
159         assert(h);
160
161         return h->algorithm;
162 }
163
164 size_t khash_get_size(khash *h) {
165         assert(h);
166
167         return h->digest_size;
168 }
169
170 int khash_reset(khash *h) {
171         ssize_t n;
172
173         assert(h);
174
175         n = send(h->fd, NULL, 0, 0);
176         if (n < 0)
177                 return -errno;
178
179         h->digest_valid = false;
180
181         return 0;
182 }
183
184 int khash_put(khash *h, const void *buffer, size_t size) {
185         ssize_t n;
186
187         assert(h);
188         assert(buffer || size == 0);
189
190         if (size <= 0)
191                 return 0;
192
193         n = send(h->fd, buffer, size, MSG_MORE);
194         if (n < 0)
195                 return -errno;
196
197         h->digest_valid = false;
198
199         return 0;
200 }
201
202 int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
203         struct msghdr mh = {
204                 mh.msg_iov = (struct iovec*) iovec,
205                 mh.msg_iovlen = n,
206         };
207         ssize_t k;
208
209         assert(h);
210         assert(iovec || n == 0);
211
212         if (n <= 0)
213                 return 0;
214
215         k = sendmsg(h->fd, &mh, MSG_MORE);
216         if (k < 0)
217                 return -errno;
218
219         h->digest_valid = false;
220
221         return 0;
222 }
223
224 static int retrieve_digest(khash *h) {
225         ssize_t n;
226
227         assert(h);
228
229         if (h->digest_valid)
230                 return 0;
231
232         n = recv(h->fd, h->digest, h->digest_size, 0);
233         if (n < 0)
234                 return n;
235         if ((size_t) n != h->digest_size) /* digest size changed? */
236                 return -EIO;
237
238         h->digest_valid = true;
239
240         return 0;
241 }
242
243 int khash_digest_data(khash *h, const void **ret) {
244         int r;
245
246         assert(h);
247         assert(ret);
248
249         r = retrieve_digest(h);
250         if (r < 0)
251                 return r;
252
253         *ret = h->digest;
254         return 0;
255 }
256
257 int khash_digest_string(khash *h, char **ret) {
258         int r;
259         char *p;
260
261         assert(h);
262         assert(ret);
263
264         r = retrieve_digest(h);
265         if (r < 0)
266                 return r;
267
268         p = hexmem(h->digest, h->digest_size);
269         if (!p)
270                 return -ENOMEM;
271
272         *ret = p;
273         return 0;
274 }