xref: /freebsd/sbin/hastd/hast_proto.c (revision 4ed925457ab06e83238a5db33e89ccc94b99a713)
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 #include <openssl/sha.h>
41 
42 #include <hast.h>
43 #include <ebuf.h>
44 #include <nv.h>
45 #include <pjdlog.h>
46 #include <proto.h>
47 
48 #include "hast_proto.h"
49 
50 struct hast_main_header {
51 	/* Protocol version. */
52 	uint8_t		version;
53 	/* Size of nv headers. */
54 	uint32_t	size;
55 } __packed;
56 
57 typedef int hps_send_t(struct hast_resource *, struct nv *nv, void **, size_t *, bool *);
58 typedef int hps_recv_t(struct hast_resource *, struct nv *nv, void **, size_t *, bool *);
59 
60 struct hast_pipe_stage {
61 	const char	*hps_name;
62 	hps_send_t	*hps_send;
63 	hps_recv_t	*hps_recv;
64 };
65 
66 static int compression_send(struct hast_resource *res, struct nv *nv,
67     void **datap, size_t *sizep, bool *freedatap);
68 static int compression_recv(struct hast_resource *res, struct nv *nv,
69     void **datap, size_t *sizep, bool *freedatap);
70 static int checksum_send(struct hast_resource *res, struct nv *nv,
71     void **datap, size_t *sizep, bool *freedatap);
72 static int checksum_recv(struct hast_resource *res, struct nv *nv,
73     void **datap, size_t *sizep, bool *freedatap);
74 
75 static struct hast_pipe_stage pipeline[] = {
76 	{ "compression", compression_send, compression_recv },
77 	{ "checksum", checksum_send, checksum_recv }
78 };
79 
80 static int
81 compression_send(struct hast_resource *res, struct nv *nv, void **datap,
82     size_t *sizep, bool *freedatap)
83 {
84 	unsigned char *newbuf;
85 
86 	res = res;	/* TODO */
87 
88 	/*
89 	 * TODO: For now we emulate compression.
90 	 * At 80% probability we succeed to compress data, which means we
91 	 * allocate new buffer, copy the data over set *freedatap to true.
92 	 */
93 
94 	if (arc4random_uniform(100) < 80) {
95 		uint32_t *origsize;
96 
97 		/*
98 		 * Compression succeeded (but we will grow by 4 bytes, not
99 		 * shrink for now).
100 		 */
101 		newbuf = malloc(sizeof(uint32_t) + *sizep);
102 		if (newbuf == NULL)
103 			return (-1);
104 		origsize = (void *)newbuf;
105 		*origsize = htole32((uint32_t)*sizep);
106 		nv_add_string(nv, "null", "compression");
107 		if (nv_error(nv) != 0) {
108 			free(newbuf);
109 			errno = nv_error(nv);
110 			return (-1);
111 		}
112 		bcopy(*datap, newbuf + sizeof(uint32_t), *sizep);
113 		if (*freedatap)
114 			free(*datap);
115 		*freedatap = true;
116 		*datap = newbuf;
117 		*sizep = sizeof(uint32_t) + *sizep;
118 	} else {
119 		/*
120 		 * Compression failed, so we leave everything as it was.
121 		 * It is not critical for compression to succeed.
122 		 */
123 	}
124 
125 	return (0);
126 }
127 
128 static int
129 compression_recv(struct hast_resource *res, struct nv *nv, void **datap,
130     size_t *sizep, bool *freedatap)
131 {
132 	unsigned char *newbuf;
133 	const char *algo;
134 	size_t origsize;
135 
136 	res = res;	/* TODO */
137 
138 	/*
139 	 * TODO: For now we emulate compression.
140 	 */
141 
142 	algo = nv_get_string(nv, "compression");
143 	if (algo == NULL)
144 		return (0);	/* No compression. */
145 	if (strcmp(algo, "null") != 0) {
146 		pjdlog_error("Unknown compression algorithm '%s'.", algo);
147 		return (-1);	/* Unknown compression algorithm. */
148 	}
149 
150 	origsize = le32toh(*(uint32_t *)*datap);
151 	newbuf = malloc(origsize);
152 	if (newbuf == NULL)
153 		return (-1);
154 	bcopy((unsigned char *)*datap + sizeof(uint32_t), newbuf, origsize);
155 	if (*freedatap)
156 		free(*datap);
157 	*freedatap = true;
158 	*datap = newbuf;
159 	*sizep = origsize;
160 
161 	return (0);
162 }
163 
164 static int
165 checksum_send(struct hast_resource *res, struct nv *nv, void **datap,
166     size_t *sizep, bool *freedatap __unused)
167 {
168 	unsigned char hash[SHA256_DIGEST_LENGTH];
169 	SHA256_CTX ctx;
170 
171 	res = res;	/* TODO */
172 
173 	SHA256_Init(&ctx);
174 	SHA256_Update(&ctx, *datap, *sizep);
175 	SHA256_Final(hash, &ctx);
176 
177 	nv_add_string(nv, "sha256", "checksum");
178 	nv_add_uint8_array(nv, hash, sizeof(hash), "hash");
179 
180 	return (0);
181 }
182 
183 static int
184 checksum_recv(struct hast_resource *res, struct nv *nv, void **datap,
185     size_t *sizep, bool *freedatap __unused)
186 {
187 	unsigned char chash[SHA256_DIGEST_LENGTH];
188 	const unsigned char *rhash;
189 	SHA256_CTX ctx;
190 	const char *algo;
191 	size_t size;
192 
193 	res = res;	/* TODO */
194 
195 	algo = nv_get_string(nv, "checksum");
196 	if (algo == NULL)
197 		return (0);	/* No checksum. */
198 	if (strcmp(algo, "sha256") != 0) {
199 		pjdlog_error("Unknown checksum algorithm '%s'.", algo);
200 		return (-1);	/* Unknown checksum algorithm. */
201 	}
202 	rhash = nv_get_uint8_array(nv, &size, "hash");
203 	if (rhash == NULL) {
204 		pjdlog_error("Checksum algorithm is present, but hash is missing.");
205 		return (-1);	/* Hash not found. */
206 	}
207 	if (size != sizeof(chash)) {
208 		pjdlog_error("Invalid hash size (%zu) for %s, should be %zu.",
209 		    size, algo, sizeof(chash));
210 		return (-1);	/* Different hash size. */
211 	}
212 
213 	SHA256_Init(&ctx);
214 	SHA256_Update(&ctx, *datap, *sizep);
215 	SHA256_Final(chash, &ctx);
216 
217 	if (bcmp(rhash, chash, sizeof(chash)) != 0) {
218 		pjdlog_error("Hash mismatch.");
219 		return (-1);	/* Hash mismatch. */
220 	}
221 
222 	return (0);
223 }
224 
225 /*
226  * Send the given nv structure via conn.
227  * We keep headers in nv structure and pass data in separate argument.
228  * There can be no data at all (data is NULL then).
229  */
230 int
231 hast_proto_send(struct hast_resource *res, struct proto_conn *conn,
232     struct nv *nv, const void *data, size_t size)
233 {
234 	struct hast_main_header hdr;
235 	struct ebuf *eb;
236 	bool freedata;
237 	void *dptr, *hptr;
238 	size_t hsize;
239 	int ret;
240 
241 	dptr = (void *)(uintptr_t)data;
242 	freedata = false;
243 	ret = -1;
244 
245 	if (data != NULL) {
246 if (false) {
247 		unsigned int ii;
248 
249 		for (ii = 0; ii < sizeof(pipeline) / sizeof(pipeline[0]);
250 		    ii++) {
251 			ret = pipeline[ii].hps_send(res, nv, &dptr, &size,
252 			    &freedata);
253 			if (ret == -1)
254 				goto end;
255 		}
256 		ret = -1;
257 }
258 		nv_add_uint32(nv, size, "size");
259 		if (nv_error(nv) != 0) {
260 			errno = nv_error(nv);
261 			goto end;
262 		}
263 	}
264 
265 	eb = nv_hton(nv);
266 	if (eb == NULL)
267 		goto end;
268 
269 	hdr.version = HAST_PROTO_VERSION;
270 	hdr.size = htole32((uint32_t)ebuf_size(eb));
271 	if (ebuf_add_head(eb, &hdr, sizeof(hdr)) < 0)
272 		goto end;
273 
274 	hptr = ebuf_data(eb, &hsize);
275 	if (proto_send(conn, hptr, hsize) < 0)
276 		goto end;
277 	if (data != NULL && proto_send(conn, dptr, size) < 0)
278 		goto end;
279 
280 	ret = 0;
281 end:
282 	if (freedata)
283 		free(dptr);
284 	return (ret);
285 }
286 
287 int
288 hast_proto_recv_hdr(struct proto_conn *conn, struct nv **nvp)
289 {
290 	struct hast_main_header hdr;
291 	struct nv *nv;
292 	struct ebuf *eb;
293 	void *hptr;
294 
295 	eb = NULL;
296 	nv = NULL;
297 
298 	if (proto_recv(conn, &hdr, sizeof(hdr)) < 0)
299 		goto fail;
300 
301 	if (hdr.version != HAST_PROTO_VERSION) {
302 		errno = ERPCMISMATCH;
303 		goto fail;
304 	}
305 
306 	hdr.size = le32toh(hdr.size);
307 
308 	eb = ebuf_alloc(hdr.size);
309 	if (eb == NULL)
310 		goto fail;
311 	if (ebuf_add_tail(eb, NULL, hdr.size) < 0)
312 		goto fail;
313 	hptr = ebuf_data(eb, NULL);
314 	assert(hptr != NULL);
315 	if (proto_recv(conn, hptr, hdr.size) < 0)
316 		goto fail;
317 	nv = nv_ntoh(eb);
318 	if (nv == NULL)
319 		goto fail;
320 
321 	*nvp = nv;
322 	return (0);
323 fail:
324 	if (nv != NULL)
325 		nv_free(nv);
326 	else if (eb != NULL)
327 		ebuf_free(eb);
328 	return (-1);
329 }
330 
331 int
332 hast_proto_recv_data(struct hast_resource *res, struct proto_conn *conn,
333     struct nv *nv, void *data, size_t size)
334 {
335 	unsigned int ii;
336 	bool freedata;
337 	size_t dsize;
338 	void *dptr;
339 	int ret;
340 
341 	assert(data != NULL);
342 	assert(size > 0);
343 
344 	ret = -1;
345 	freedata = false;
346 	dptr = data;
347 
348 	dsize = nv_get_uint32(nv, "size");
349 	if (dsize == 0)
350 		(void)nv_set_error(nv, 0);
351 	else {
352 		if (proto_recv(conn, data, dsize) < 0)
353 			goto end;
354 if (false) {
355 		for (ii = sizeof(pipeline) / sizeof(pipeline[0]); ii > 0;
356 		    ii--) {
357 			assert(!"to be verified");
358 			ret = pipeline[ii - 1].hps_recv(res, nv, &dptr,
359 			    &dsize, &freedata);
360 			if (ret == -1)
361 				goto end;
362 		}
363 		ret = -1;
364 		if (dsize < size)
365 			goto end;
366 		/* TODO: 'size' doesn't seem right here. It is maximum data size. */
367 		if (dptr != data)
368 			bcopy(dptr, data, dsize);
369 }
370 	}
371 
372 	ret = 0;
373 end:
374 if (ret < 0) printf("%s:%u %s\n", __func__, __LINE__, strerror(errno));
375 	if (freedata)
376 		free(dptr);
377 	return (ret);
378 }
379 
380 int
381 hast_proto_recv(struct hast_resource *res, struct proto_conn *conn,
382     struct nv **nvp, void *data, size_t size)
383 {
384 	struct nv *nv;
385 	size_t dsize;
386 	int ret;
387 
388 	ret = hast_proto_recv_hdr(conn, &nv);
389 	if (ret < 0)
390 		return (ret);
391 	dsize = nv_get_uint32(nv, "size");
392 	if (dsize == 0)
393 		(void)nv_set_error(nv, 0);
394 	else
395 		ret = hast_proto_recv_data(res, conn, nv, data, size);
396 	if (ret < 0)
397 		nv_free(nv);
398 	else
399 		*nvp = nv;
400 	return (ret);
401 }
402