xref: /freebsd/sbin/hastd/hast_proto.c (revision e28a4053)
1 /*-
2  * Copyright (c) 2009-2010 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/endian.h>
34 
35 #include <assert.h>
36 #include <errno.h>
37 #include <string.h>
38 #include <strings.h>
39 
40 #ifdef HAVE_CRYPTO
41 #include <openssl/sha.h>
42 #endif
43 
44 #include <hast.h>
45 #include <ebuf.h>
46 #include <nv.h>
47 #include <pjdlog.h>
48 #include <proto.h>
49 
50 #include "hast_proto.h"
51 
52 struct hast_main_header {
53 	/* Protocol version. */
54 	uint8_t		version;
55 	/* Size of nv headers. */
56 	uint32_t	size;
57 } __packed;
58 
59 typedef int hps_send_t(const struct hast_resource *, struct nv *nv, void **,
60     size_t *, bool *);
61 typedef int hps_recv_t(const struct hast_resource *, struct nv *nv, void **,
62     size_t *, bool *);
63 
64 struct hast_pipe_stage {
65 	const char	*hps_name;
66 	hps_send_t	*hps_send;
67 	hps_recv_t	*hps_recv;
68 };
69 
70 static int compression_send(const struct hast_resource *res, struct nv *nv,
71     void **datap, size_t *sizep, bool *freedatap);
72 static int compression_recv(const struct hast_resource *res, struct nv *nv,
73     void **datap, size_t *sizep, bool *freedatap);
74 #ifdef HAVE_CRYPTO
75 static int checksum_send(const struct hast_resource *res, struct nv *nv,
76     void **datap, size_t *sizep, bool *freedatap);
77 static int checksum_recv(const struct hast_resource *res, struct nv *nv,
78     void **datap, size_t *sizep, bool *freedatap);
79 #endif
80 
81 static struct hast_pipe_stage pipeline[] = {
82 	{ "compression", compression_send, compression_recv },
83 #ifdef HAVE_CRYPTO
84 	{ "checksum", checksum_send, checksum_recv }
85 #endif
86 };
87 
88 static int
89 compression_send(const struct hast_resource *res, struct nv *nv, void **datap,
90     size_t *sizep, bool *freedatap)
91 {
92 	unsigned char *newbuf;
93 
94 	res = res;	/* TODO */
95 
96 	/*
97 	 * TODO: For now we emulate compression.
98 	 * At 80% probability we succeed to compress data, which means we
99 	 * allocate new buffer, copy the data over set *freedatap to true.
100 	 */
101 
102 	if (arc4random_uniform(100) < 80) {
103 		uint32_t *origsize;
104 
105 		/*
106 		 * Compression succeeded (but we will grow by 4 bytes, not
107 		 * shrink for now).
108 		 */
109 		newbuf = malloc(sizeof(uint32_t) + *sizep);
110 		if (newbuf == NULL)
111 			return (-1);
112 		origsize = (void *)newbuf;
113 		*origsize = htole32((uint32_t)*sizep);
114 		nv_add_string(nv, "null", "compression");
115 		if (nv_error(nv) != 0) {
116 			free(newbuf);
117 			errno = nv_error(nv);
118 			return (-1);
119 		}
120 		bcopy(*datap, newbuf + sizeof(uint32_t), *sizep);
121 		if (*freedatap)
122 			free(*datap);
123 		*freedatap = true;
124 		*datap = newbuf;
125 		*sizep = sizeof(uint32_t) + *sizep;
126 	} else {
127 		/*
128 		 * Compression failed, so we leave everything as it was.
129 		 * It is not critical for compression to succeed.
130 		 */
131 	}
132 
133 	return (0);
134 }
135 
136 static int
137 compression_recv(const struct hast_resource *res, struct nv *nv, void **datap,
138     size_t *sizep, bool *freedatap)
139 {
140 	unsigned char *newbuf;
141 	const char *algo;
142 	size_t origsize;
143 
144 	res = res;	/* TODO */
145 
146 	/*
147 	 * TODO: For now we emulate compression.
148 	 */
149 
150 	algo = nv_get_string(nv, "compression");
151 	if (algo == NULL)
152 		return (0);	/* No compression. */
153 	if (strcmp(algo, "null") != 0) {
154 		pjdlog_error("Unknown compression algorithm '%s'.", algo);
155 		return (-1);	/* Unknown compression algorithm. */
156 	}
157 
158 	origsize = le32toh(*(uint32_t *)*datap);
159 	newbuf = malloc(origsize);
160 	if (newbuf == NULL)
161 		return (-1);
162 	bcopy((unsigned char *)*datap + sizeof(uint32_t), newbuf, origsize);
163 	if (*freedatap)
164 		free(*datap);
165 	*freedatap = true;
166 	*datap = newbuf;
167 	*sizep = origsize;
168 
169 	return (0);
170 }
171 
172 #ifdef HAVE_CRYPTO
173 static int
174 checksum_send(const struct hast_resource *res, struct nv *nv, void **datap,
175     size_t *sizep, bool *freedatap __unused)
176 {
177 	unsigned char hash[SHA256_DIGEST_LENGTH];
178 	SHA256_CTX ctx;
179 
180 	res = res;	/* TODO */
181 
182 	SHA256_Init(&ctx);
183 	SHA256_Update(&ctx, *datap, *sizep);
184 	SHA256_Final(hash, &ctx);
185 
186 	nv_add_string(nv, "sha256", "checksum");
187 	nv_add_uint8_array(nv, hash, sizeof(hash), "hash");
188 
189 	return (0);
190 }
191 
192 static int
193 checksum_recv(const struct hast_resource *res, struct nv *nv, void **datap,
194     size_t *sizep, bool *freedatap __unused)
195 {
196 	unsigned char chash[SHA256_DIGEST_LENGTH];
197 	const unsigned char *rhash;
198 	SHA256_CTX ctx;
199 	const char *algo;
200 	size_t size;
201 
202 	res = res;	/* TODO */
203 
204 	algo = nv_get_string(nv, "checksum");
205 	if (algo == NULL)
206 		return (0);	/* No checksum. */
207 	if (strcmp(algo, "sha256") != 0) {
208 		pjdlog_error("Unknown checksum algorithm '%s'.", algo);
209 		return (-1);	/* Unknown checksum algorithm. */
210 	}
211 	rhash = nv_get_uint8_array(nv, &size, "hash");
212 	if (rhash == NULL) {
213 		pjdlog_error("Checksum algorithm is present, but hash is missing.");
214 		return (-1);	/* Hash not found. */
215 	}
216 	if (size != sizeof(chash)) {
217 		pjdlog_error("Invalid hash size (%zu) for %s, should be %zu.",
218 		    size, algo, sizeof(chash));
219 		return (-1);	/* Different hash size. */
220 	}
221 
222 	SHA256_Init(&ctx);
223 	SHA256_Update(&ctx, *datap, *sizep);
224 	SHA256_Final(chash, &ctx);
225 
226 	if (bcmp(rhash, chash, sizeof(chash)) != 0) {
227 		pjdlog_error("Hash mismatch.");
228 		return (-1);	/* Hash mismatch. */
229 	}
230 
231 	return (0);
232 }
233 #endif	/* HAVE_CRYPTO */
234 
235 /*
236  * Send the given nv structure via conn.
237  * We keep headers in nv structure and pass data in separate argument.
238  * There can be no data at all (data is NULL then).
239  */
240 int
241 hast_proto_send(const struct hast_resource *res, struct proto_conn *conn,
242     struct nv *nv, const void *data, size_t size)
243 {
244 	struct hast_main_header hdr;
245 	struct ebuf *eb;
246 	bool freedata;
247 	void *dptr, *hptr;
248 	size_t hsize;
249 	int ret;
250 
251 	dptr = (void *)(uintptr_t)data;
252 	freedata = false;
253 	ret = -1;
254 
255 	if (data != NULL) {
256 if (false) {
257 		unsigned int ii;
258 
259 		for (ii = 0; ii < sizeof(pipeline) / sizeof(pipeline[0]);
260 		    ii++) {
261 			ret = pipeline[ii].hps_send(res, nv, &dptr, &size,
262 			    &freedata);
263 			if (ret == -1)
264 				goto end;
265 		}
266 		ret = -1;
267 }
268 		nv_add_uint32(nv, size, "size");
269 		if (nv_error(nv) != 0) {
270 			errno = nv_error(nv);
271 			goto end;
272 		}
273 	}
274 
275 	eb = nv_hton(nv);
276 	if (eb == NULL)
277 		goto end;
278 
279 	hdr.version = HAST_PROTO_VERSION;
280 	hdr.size = htole32((uint32_t)ebuf_size(eb));
281 	if (ebuf_add_head(eb, &hdr, sizeof(hdr)) < 0)
282 		goto end;
283 
284 	hptr = ebuf_data(eb, &hsize);
285 	if (proto_send(conn, hptr, hsize) < 0)
286 		goto end;
287 	if (data != NULL && proto_send(conn, dptr, size) < 0)
288 		goto end;
289 
290 	ret = 0;
291 end:
292 	if (freedata)
293 		free(dptr);
294 	return (ret);
295 }
296 
297 int
298 hast_proto_recv_hdr(const struct proto_conn *conn, struct nv **nvp)
299 {
300 	struct hast_main_header hdr;
301 	struct nv *nv;
302 	struct ebuf *eb;
303 	void *hptr;
304 
305 	eb = NULL;
306 	nv = NULL;
307 
308 	if (proto_recv(conn, &hdr, sizeof(hdr)) < 0)
309 		goto fail;
310 
311 	if (hdr.version != HAST_PROTO_VERSION) {
312 		errno = ERPCMISMATCH;
313 		goto fail;
314 	}
315 
316 	hdr.size = le32toh(hdr.size);
317 
318 	eb = ebuf_alloc(hdr.size);
319 	if (eb == NULL)
320 		goto fail;
321 	if (ebuf_add_tail(eb, NULL, hdr.size) < 0)
322 		goto fail;
323 	hptr = ebuf_data(eb, NULL);
324 	assert(hptr != NULL);
325 	if (proto_recv(conn, hptr, hdr.size) < 0)
326 		goto fail;
327 	nv = nv_ntoh(eb);
328 	if (nv == NULL)
329 		goto fail;
330 
331 	*nvp = nv;
332 	return (0);
333 fail:
334 	if (eb != NULL)
335 		ebuf_free(eb);
336 	return (-1);
337 }
338 
339 int
340 hast_proto_recv_data(const struct hast_resource *res, struct proto_conn *conn,
341     struct nv *nv, void *data, size_t size)
342 {
343 	unsigned int ii;
344 	bool freedata;
345 	size_t dsize;
346 	void *dptr;
347 	int ret;
348 
349 	assert(data != NULL);
350 	assert(size > 0);
351 
352 	ret = -1;
353 	freedata = false;
354 	dptr = data;
355 
356 	dsize = nv_get_uint32(nv, "size");
357 	if (dsize == 0)
358 		(void)nv_set_error(nv, 0);
359 	else {
360 		if (proto_recv(conn, data, dsize) < 0)
361 			goto end;
362 if (false) {
363 		for (ii = sizeof(pipeline) / sizeof(pipeline[0]); ii > 0;
364 		    ii--) {
365 			assert(!"to be verified");
366 			ret = pipeline[ii - 1].hps_recv(res, nv, &dptr,
367 			    &dsize, &freedata);
368 			if (ret == -1)
369 				goto end;
370 		}
371 		ret = -1;
372 		if (dsize < size)
373 			goto end;
374 		/* TODO: 'size' doesn't seem right here. It is maximum data size. */
375 		if (dptr != data)
376 			bcopy(dptr, data, dsize);
377 }
378 	}
379 
380 	ret = 0;
381 end:
382 if (ret < 0) printf("%s:%u %s\n", __func__, __LINE__, strerror(errno));
383 	if (freedata)
384 		free(dptr);
385 	return (ret);
386 }
387 
388 int
389 hast_proto_recv(const struct hast_resource *res, struct proto_conn *conn,
390     struct nv **nvp, void *data, size_t size)
391 {
392 	struct nv *nv;
393 	size_t dsize;
394 	int ret;
395 
396 	ret = hast_proto_recv_hdr(conn, &nv);
397 	if (ret < 0)
398 		return (ret);
399 	dsize = nv_get_uint32(nv, "size");
400 	if (dsize == 0)
401 		(void)nv_set_error(nv, 0);
402 	else
403 		ret = hast_proto_recv_data(res, conn, nv, data, size);
404 	if (ret < 0)
405 		nv_free(nv);
406 	else
407 		*nvp = nv;
408 	return (ret);
409 }
410