1/*	$NetBSD: base64.c,v 1.1 2024/02/18 20:57:48 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16/*! \file */
17
18#include <stdbool.h>
19
20#include <isc/base64.h>
21#include <isc/buffer.h>
22#include <isc/lex.h>
23#include <isc/string.h>
24#include <isc/util.h>
25
26#define RETERR(x)                        \
27	do {                             \
28		isc_result_t _r = (x);   \
29		if (_r != ISC_R_SUCCESS) \
30			return ((_r));   \
31	} while (0)
32
33/*@{*/
34/*!
35 * These static functions are also present in lib/dns/rdata.c.  I'm not
36 * sure where they should go. -- bwelling
37 */
38static isc_result_t
39str_totext(const char *source, isc_buffer_t *target);
40
41static isc_result_t
42mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
43
44static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw"
45			     "xyz0123456789+/=";
46/*@}*/
47
48isc_result_t
49isc_base64_totext(isc_region_t *source, int wordlength, const char *wordbreak,
50		  isc_buffer_t *target) {
51	char buf[5];
52	unsigned int loops = 0;
53
54	if (wordlength < 4) {
55		wordlength = 4;
56	}
57
58	memset(buf, 0, sizeof(buf));
59	while (source->length > 2) {
60		buf[0] = base64[(source->base[0] >> 2) & 0x3f];
61		buf[1] = base64[((source->base[0] << 4) & 0x30) |
62				((source->base[1] >> 4) & 0x0f)];
63		buf[2] = base64[((source->base[1] << 2) & 0x3c) |
64				((source->base[2] >> 6) & 0x03)];
65		buf[3] = base64[source->base[2] & 0x3f];
66		RETERR(str_totext(buf, target));
67		isc_region_consume(source, 3);
68
69		loops++;
70		if (source->length != 0 && (int)((loops + 1) * 4) >= wordlength)
71		{
72			loops = 0;
73			RETERR(str_totext(wordbreak, target));
74		}
75	}
76	if (source->length == 2) {
77		buf[0] = base64[(source->base[0] >> 2) & 0x3f];
78		buf[1] = base64[((source->base[0] << 4) & 0x30) |
79				((source->base[1] >> 4) & 0x0f)];
80		buf[2] = base64[((source->base[1] << 2) & 0x3c)];
81		buf[3] = '=';
82		RETERR(str_totext(buf, target));
83		isc_region_consume(source, 2);
84	} else if (source->length == 1) {
85		buf[0] = base64[(source->base[0] >> 2) & 0x3f];
86		buf[1] = base64[((source->base[0] << 4) & 0x30)];
87		buf[2] = buf[3] = '=';
88		RETERR(str_totext(buf, target));
89		isc_region_consume(source, 1);
90	}
91	return (ISC_R_SUCCESS);
92}
93
94/*%
95 * State of a base64 decoding process in progress.
96 */
97typedef struct {
98	int length;	      /*%< Desired length of binary data or -1 */
99	isc_buffer_t *target; /*%< Buffer for resulting binary data */
100	int digits;	      /*%< Number of buffered base64 digits */
101	bool seen_end;	      /*%< True if "=" end marker seen */
102	int val[4];
103} base64_decode_ctx_t;
104
105static void
106base64_decode_init(base64_decode_ctx_t *ctx, int length, isc_buffer_t *target) {
107	ctx->digits = 0;
108	ctx->seen_end = false;
109	ctx->length = length;
110	ctx->target = target;
111}
112
113static isc_result_t
114base64_decode_char(base64_decode_ctx_t *ctx, int c) {
115	const char *s;
116
117	if (ctx->seen_end) {
118		return (ISC_R_BADBASE64);
119	}
120	if ((s = strchr(base64, c)) == NULL) {
121		return (ISC_R_BADBASE64);
122	}
123	ctx->val[ctx->digits++] = (int)(s - base64);
124	if (ctx->digits == 4) {
125		int n;
126		unsigned char buf[3];
127		if (ctx->val[0] == 64 || ctx->val[1] == 64) {
128			return (ISC_R_BADBASE64);
129		}
130		if (ctx->val[2] == 64 && ctx->val[3] != 64) {
131			return (ISC_R_BADBASE64);
132		}
133		/*
134		 * Check that bits that should be zero are.
135		 */
136		if (ctx->val[2] == 64 && (ctx->val[1] & 0xf) != 0) {
137			return (ISC_R_BADBASE64);
138		}
139		/*
140		 * We don't need to test for ctx->val[2] != 64 as
141		 * the bottom two bits of 64 are zero.
142		 */
143		if (ctx->val[3] == 64 && (ctx->val[2] & 0x3) != 0) {
144			return (ISC_R_BADBASE64);
145		}
146		n = (ctx->val[2] == 64) ? 1 : (ctx->val[3] == 64) ? 2 : 3;
147		if (n != 3) {
148			ctx->seen_end = true;
149			if (ctx->val[2] == 64) {
150				ctx->val[2] = 0;
151			}
152			if (ctx->val[3] == 64) {
153				ctx->val[3] = 0;
154			}
155		}
156		buf[0] = (ctx->val[0] << 2) | (ctx->val[1] >> 4);
157		buf[1] = (ctx->val[1] << 4) | (ctx->val[2] >> 2);
158		buf[2] = (ctx->val[2] << 6) | (ctx->val[3]);
159		RETERR(mem_tobuffer(ctx->target, buf, n));
160		if (ctx->length >= 0) {
161			if (n > ctx->length) {
162				return (ISC_R_BADBASE64);
163			} else {
164				ctx->length -= n;
165			}
166		}
167		ctx->digits = 0;
168	}
169	return (ISC_R_SUCCESS);
170}
171
172static isc_result_t
173base64_decode_finish(base64_decode_ctx_t *ctx) {
174	if (ctx->length > 0) {
175		return (ISC_R_UNEXPECTEDEND);
176	}
177	if (ctx->digits != 0) {
178		return (ISC_R_BADBASE64);
179	}
180	return (ISC_R_SUCCESS);
181}
182
183isc_result_t
184isc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
185	unsigned int before, after;
186	base64_decode_ctx_t ctx;
187	isc_textregion_t *tr;
188	isc_token_t token;
189	bool eol;
190
191	REQUIRE(length >= -2);
192
193	base64_decode_init(&ctx, length, target);
194
195	before = isc_buffer_usedlength(target);
196	while (!ctx.seen_end && (ctx.length != 0)) {
197		unsigned int i;
198
199		if (length > 0) {
200			eol = false;
201		} else {
202			eol = true;
203		}
204		RETERR(isc_lex_getmastertoken(lexer, &token,
205					      isc_tokentype_string, eol));
206		if (token.type != isc_tokentype_string) {
207			break;
208		}
209		tr = &token.value.as_textregion;
210		for (i = 0; i < tr->length; i++) {
211			RETERR(base64_decode_char(&ctx, tr->base[i]));
212		}
213	}
214	after = isc_buffer_usedlength(target);
215	if (ctx.length < 0 && !ctx.seen_end) {
216		isc_lex_ungettoken(lexer, &token);
217	}
218	RETERR(base64_decode_finish(&ctx));
219	if (length == -2 && before == after) {
220		return (ISC_R_UNEXPECTEDEND);
221	}
222	return (ISC_R_SUCCESS);
223}
224
225isc_result_t
226isc_base64_decodestring(const char *cstr, isc_buffer_t *target) {
227	base64_decode_ctx_t ctx;
228
229	base64_decode_init(&ctx, -1, target);
230	for (;;) {
231		int c = *cstr++;
232		if (c == '\0') {
233			break;
234		}
235		if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
236			continue;
237		}
238		RETERR(base64_decode_char(&ctx, c));
239	}
240	RETERR(base64_decode_finish(&ctx));
241	return (ISC_R_SUCCESS);
242}
243
244static isc_result_t
245str_totext(const char *source, isc_buffer_t *target) {
246	unsigned int l;
247	isc_region_t region;
248
249	isc_buffer_availableregion(target, &region);
250	l = strlen(source);
251
252	if (l > region.length) {
253		return (ISC_R_NOSPACE);
254	}
255
256	memmove(region.base, source, l);
257	isc_buffer_add(target, l);
258	return (ISC_R_SUCCESS);
259}
260
261static isc_result_t
262mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
263	isc_region_t tr;
264
265	isc_buffer_availableregion(target, &tr);
266	if (length > tr.length) {
267		return (ISC_R_NOSPACE);
268	}
269	memmove(tr.base, base, length);
270	isc_buffer_add(target, length);
271	return (ISC_R_SUCCESS);
272}
273