hast_proto.c revision 204076
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: head/sbin/hastd/hast_proto.c 204076 2010-02-18 23:16:19Z pjd $");
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
50struct hast_main_header {
51	/* Protocol version. */
52	uint8_t		version;
53	/* Size of nv headers. */
54	uint32_t	size;
55} __packed;
56
57typedef int hps_send_t(struct hast_resource *, struct nv *nv, void **, size_t *, bool *);
58typedef int hps_recv_t(struct hast_resource *, struct nv *nv, void **, size_t *, bool *);
59
60struct hast_pipe_stage {
61	const char	*hps_name;
62	hps_send_t	*hps_send;
63	hps_recv_t	*hps_recv;
64};
65
66static int compression_send(struct hast_resource *res, struct nv *nv,
67    void **datap, size_t *sizep, bool *freedatap);
68static int compression_recv(struct hast_resource *res, struct nv *nv,
69    void **datap, size_t *sizep, bool *freedatap);
70static int checksum_send(struct hast_resource *res, struct nv *nv,
71    void **datap, size_t *sizep, bool *freedatap);
72static int checksum_recv(struct hast_resource *res, struct nv *nv,
73    void **datap, size_t *sizep, bool *freedatap);
74
75static struct hast_pipe_stage pipeline[] = {
76	{ "compression", compression_send, compression_recv },
77	{ "checksum", checksum_send, checksum_recv }
78};
79
80static int
81compression_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
128static int
129compression_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
164static int
165checksum_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
183static int
184checksum_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 */
230int
231hast_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) {
246if (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;
281end:
282	if (freedata)
283		free(dptr);
284	return (ret);
285}
286
287int
288hast_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);
323fail:
324	if (nv != NULL)
325		nv_free(nv);
326	else if (eb != NULL)
327		ebuf_free(eb);
328	return (-1);
329}
330
331int
332hast_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;
354if (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;
373end:
374if (ret < 0) printf("%s:%u %s\n", __func__, __LINE__, strerror(errno));
375	if (freedata)
376		free(dptr);
377	return (ret);
378}
379
380int
381hast_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