1290001Sglebius/*
2290001Sglebius * Copyright (C) 2004, 2005, 2007, 2009  Internet Systems Consortium, Inc. ("ISC")
3290001Sglebius * Copyright (C) 1998-2001, 2003  Internet Software Consortium.
4290001Sglebius *
5290001Sglebius * Permission to use, copy, modify, and/or distribute this software for any
6290001Sglebius * purpose with or without fee is hereby granted, provided that the above
7290001Sglebius * copyright notice and this permission notice appear in all copies.
8290001Sglebius *
9290001Sglebius * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10290001Sglebius * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11290001Sglebius * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12290001Sglebius * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13290001Sglebius * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14290001Sglebius * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15290001Sglebius * PERFORMANCE OF THIS SOFTWARE.
16290001Sglebius */
17290001Sglebius
18290001Sglebius/* $Id: base64.c,v 1.34 2009/10/21 23:48:05 tbox Exp $ */
19290001Sglebius
20290001Sglebius/*! \file */
21290001Sglebius
22290001Sglebius#include <config.h>
23290001Sglebius
24290001Sglebius#include <isc/base64.h>
25290001Sglebius#include <isc/buffer.h>
26290001Sglebius#include <isc/lex.h>
27290001Sglebius#include <isc/string.h>
28290001Sglebius#include <isc/util.h>
29290001Sglebius
30290001Sglebius#define RETERR(x) do { \
31290001Sglebius	isc_result_t _r = (x); \
32290001Sglebius	if (_r != ISC_R_SUCCESS) \
33290001Sglebius		return (_r); \
34290001Sglebius	} while (0)
35290001Sglebius
36290001Sglebius
37290001Sglebius/*@{*/
38290001Sglebius/*!
39290001Sglebius * These static functions are also present in lib/dns/rdata.c.  I'm not
40290001Sglebius * sure where they should go. -- bwelling
41290001Sglebius */
42290001Sglebiusstatic isc_result_t
43290001Sglebiusstr_totext(const char *source, isc_buffer_t *target);
44290001Sglebius
45290001Sglebiusstatic isc_result_t
46290001Sglebiusmem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
47290001Sglebius
48290001Sglebiusstatic const char base64[] =
49290001Sglebius	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
50290001Sglebius/*@}*/
51290001Sglebius
52290001Sglebiusisc_result_t
53290001Sglebiusisc_base64_totext(isc_region_t *source, int wordlength,
54290001Sglebius		  const char *wordbreak, isc_buffer_t *target)
55290001Sglebius{
56290001Sglebius	char buf[5];
57290001Sglebius	unsigned int loops = 0;
58290001Sglebius
59290001Sglebius	if (wordlength < 4)
60290001Sglebius		wordlength = 4;
61290001Sglebius
62290001Sglebius	memset(buf, 0, sizeof(buf));
63290001Sglebius	while (source->length > 2) {
64290001Sglebius		buf[0] = base64[(source->base[0]>>2)&0x3f];
65290001Sglebius		buf[1] = base64[((source->base[0]<<4)&0x30)|
66290001Sglebius				((source->base[1]>>4)&0x0f)];
67290001Sglebius		buf[2] = base64[((source->base[1]<<2)&0x3c)|
68290001Sglebius				((source->base[2]>>6)&0x03)];
69290001Sglebius		buf[3] = base64[source->base[2]&0x3f];
70290001Sglebius		RETERR(str_totext(buf, target));
71290001Sglebius		isc_region_consume(source, 3);
72290001Sglebius
73290001Sglebius		loops++;
74290001Sglebius		if (source->length != 0 &&
75290001Sglebius		    (int)((loops + 1) * 4) >= wordlength)
76290001Sglebius		{
77290001Sglebius			loops = 0;
78290001Sglebius			RETERR(str_totext(wordbreak, target));
79290001Sglebius		}
80290001Sglebius	}
81290001Sglebius	if (source->length == 2) {
82290001Sglebius		buf[0] = base64[(source->base[0]>>2)&0x3f];
83290001Sglebius		buf[1] = base64[((source->base[0]<<4)&0x30)|
84290001Sglebius				((source->base[1]>>4)&0x0f)];
85290001Sglebius		buf[2] = base64[((source->base[1]<<2)&0x3c)];
86290001Sglebius		buf[3] = '=';
87290001Sglebius		RETERR(str_totext(buf, target));
88290001Sglebius		isc_region_consume(source, 2);
89290001Sglebius	} else if (source->length == 1) {
90290001Sglebius		buf[0] = base64[(source->base[0]>>2)&0x3f];
91290001Sglebius		buf[1] = base64[((source->base[0]<<4)&0x30)];
92290001Sglebius		buf[2] = buf[3] = '=';
93290001Sglebius		RETERR(str_totext(buf, target));
94290001Sglebius		isc_region_consume(source, 1);
95290001Sglebius	}
96290001Sglebius	return (ISC_R_SUCCESS);
97290001Sglebius}
98290001Sglebius
99290001Sglebius/*%
100290001Sglebius * State of a base64 decoding process in progress.
101290001Sglebius */
102290001Sglebiustypedef struct {
103290001Sglebius	int length;		/*%< Desired length of binary data or -1 */
104290001Sglebius	isc_buffer_t *target;	/*%< Buffer for resulting binary data */
105290001Sglebius	int digits;		/*%< Number of buffered base64 digits */
106290001Sglebius	isc_boolean_t seen_end;	/*%< True if "=" end marker seen */
107290001Sglebius	int val[4];
108290001Sglebius} base64_decode_ctx_t;
109290001Sglebius
110290001Sglebiusstatic inline void
111290001Sglebiusbase64_decode_init(base64_decode_ctx_t *ctx, int length, isc_buffer_t *target)
112290001Sglebius{
113290001Sglebius	ctx->digits = 0;
114290001Sglebius	ctx->seen_end = ISC_FALSE;
115290001Sglebius	ctx->length = length;
116290001Sglebius	ctx->target = target;
117290001Sglebius}
118290001Sglebius
119290001Sglebiusstatic inline isc_result_t
120290001Sglebiusbase64_decode_char(base64_decode_ctx_t *ctx, int c) {
121290001Sglebius	char *s;
122290001Sglebius
123290001Sglebius	if (ctx->seen_end)
124290001Sglebius		return (ISC_R_BADBASE64);
125290001Sglebius	if ((s = strchr(base64, c)) == NULL)
126290001Sglebius		return (ISC_R_BADBASE64);
127290001Sglebius	ctx->val[ctx->digits++] = s - base64;
128290001Sglebius	if (ctx->digits == 4) {
129290001Sglebius		int n;
130290001Sglebius		unsigned char buf[3];
131290001Sglebius		if (ctx->val[0] == 64 || ctx->val[1] == 64)
132290001Sglebius			return (ISC_R_BADBASE64);
133290001Sglebius		if (ctx->val[2] == 64 && ctx->val[3] != 64)
134290001Sglebius			return (ISC_R_BADBASE64);
135290001Sglebius		/*
136290001Sglebius		 * Check that bits that should be zero are.
137290001Sglebius		 */
138290001Sglebius		if (ctx->val[2] == 64 && (ctx->val[1] & 0xf) != 0)
139290001Sglebius			return (ISC_R_BADBASE64);
140290001Sglebius		/*
141290001Sglebius		 * We don't need to test for ctx->val[2] != 64 as
142290001Sglebius		 * the bottom two bits of 64 are zero.
143290001Sglebius		 */
144290001Sglebius		if (ctx->val[3] == 64 && (ctx->val[2] & 0x3) != 0)
145290001Sglebius			return (ISC_R_BADBASE64);
146290001Sglebius		n = (ctx->val[2] == 64) ? 1 :
147290001Sglebius			(ctx->val[3] == 64) ? 2 : 3;
148290001Sglebius		if (n != 3) {
149290001Sglebius			ctx->seen_end = ISC_TRUE;
150290001Sglebius			if (ctx->val[2] == 64)
151290001Sglebius				ctx->val[2] = 0;
152290001Sglebius			if (ctx->val[3] == 64)
153290001Sglebius				ctx->val[3] = 0;
154290001Sglebius		}
155290001Sglebius		buf[0] = (ctx->val[0]<<2)|(ctx->val[1]>>4);
156290001Sglebius		buf[1] = (ctx->val[1]<<4)|(ctx->val[2]>>2);
157290001Sglebius		buf[2] = (ctx->val[2]<<6)|(ctx->val[3]);
158290001Sglebius		RETERR(mem_tobuffer(ctx->target, buf, n));
159290001Sglebius		if (ctx->length >= 0) {
160290001Sglebius			if (n > ctx->length)
161290001Sglebius				return (ISC_R_BADBASE64);
162290001Sglebius			else
163290001Sglebius				ctx->length -= n;
164290001Sglebius		}
165290001Sglebius		ctx->digits = 0;
166290001Sglebius	}
167290001Sglebius	return (ISC_R_SUCCESS);
168290001Sglebius}
169290001Sglebius
170290001Sglebiusstatic inline isc_result_t
171290001Sglebiusbase64_decode_finish(base64_decode_ctx_t *ctx) {
172290001Sglebius	if (ctx->length > 0)
173290001Sglebius		return (ISC_R_UNEXPECTEDEND);
174290001Sglebius	if (ctx->digits != 0)
175290001Sglebius		return (ISC_R_BADBASE64);
176290001Sglebius	return (ISC_R_SUCCESS);
177290001Sglebius}
178290001Sglebius
179290001Sglebiusisc_result_t
180290001Sglebiusisc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
181290001Sglebius	base64_decode_ctx_t ctx;
182290001Sglebius	isc_textregion_t *tr;
183290001Sglebius	isc_token_t token;
184290001Sglebius	isc_boolean_t eol;
185290001Sglebius
186290001Sglebius	base64_decode_init(&ctx, length, target);
187290001Sglebius
188290001Sglebius	while (!ctx.seen_end && (ctx.length != 0)) {
189290001Sglebius		unsigned int i;
190290001Sglebius
191290001Sglebius		if (length > 0)
192290001Sglebius			eol = ISC_FALSE;
193290001Sglebius		else
194290001Sglebius			eol = ISC_TRUE;
195290001Sglebius		RETERR(isc_lex_getmastertoken(lexer, &token,
196290001Sglebius					      isc_tokentype_string, eol));
197290001Sglebius		if (token.type != isc_tokentype_string)
198290001Sglebius			break;
199290001Sglebius		tr = &token.value.as_textregion;
200290001Sglebius		for (i = 0; i < tr->length; i++)
201290001Sglebius			RETERR(base64_decode_char(&ctx, tr->base[i]));
202290001Sglebius	}
203290001Sglebius	if (ctx.length < 0 && !ctx.seen_end)
204290001Sglebius		isc_lex_ungettoken(lexer, &token);
205290001Sglebius	RETERR(base64_decode_finish(&ctx));
206290001Sglebius	return (ISC_R_SUCCESS);
207290001Sglebius}
208290001Sglebius
209290001Sglebiusisc_result_t
210290001Sglebiusisc_base64_decodestring(const char *cstr, isc_buffer_t *target) {
211290001Sglebius	base64_decode_ctx_t ctx;
212290001Sglebius
213290001Sglebius	base64_decode_init(&ctx, -1, target);
214290001Sglebius	for (;;) {
215290001Sglebius		int c = *cstr++;
216290001Sglebius		if (c == '\0')
217290001Sglebius			break;
218290001Sglebius		if (c == ' ' || c == '\t' || c == '\n' || c== '\r')
219290001Sglebius			continue;
220290001Sglebius		RETERR(base64_decode_char(&ctx, c));
221290001Sglebius	}
222290001Sglebius	RETERR(base64_decode_finish(&ctx));
223290001Sglebius	return (ISC_R_SUCCESS);
224290001Sglebius}
225290001Sglebius
226290001Sglebiusstatic isc_result_t
227290001Sglebiusstr_totext(const char *source, isc_buffer_t *target) {
228290001Sglebius	unsigned int l;
229290001Sglebius	isc_region_t region;
230290001Sglebius
231290001Sglebius	isc_buffer_availableregion(target, &region);
232290001Sglebius	l = strlen(source);
233290001Sglebius
234290001Sglebius	if (l > region.length)
235290001Sglebius		return (ISC_R_NOSPACE);
236290001Sglebius
237290001Sglebius	memcpy(region.base, source, l);
238290001Sglebius	isc_buffer_add(target, l);
239290001Sglebius	return (ISC_R_SUCCESS);
240290001Sglebius}
241290001Sglebius
242290001Sglebiusstatic isc_result_t
243290001Sglebiusmem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
244290001Sglebius	isc_region_t tr;
245290001Sglebius
246290001Sglebius	isc_buffer_availableregion(target, &tr);
247290001Sglebius	if (length > tr.length)
248290001Sglebius		return (ISC_R_NOSPACE);
249290001Sglebius	memcpy(tr.base, base, length);
250290001Sglebius	isc_buffer_add(target, length);
251290001Sglebius	return (ISC_R_SUCCESS);
252290001Sglebius}
253