1/*	$NetBSD: dict_file.c,v 1.3 2022/10/08 16:12:50 christos Exp $	*/
2
3/*++
4/* NAME
5/*	dict_file_to_buf 3
6/* SUMMARY
7/*	include content from file as blob
8/* SYNOPSIS
9/*	#include <dict.h>
10/*
11/*	VSTRING	*dict_file_to_buf(
12/*	DICT	*dict,
13/*	const char *pathnames)
14/*
15/*	VSTRING	*dict_file_to_b64(
16/*	DICT	*dict,
17/*	const char *pathnames)
18/*
19/*	VSTRING	*dict_file_from_b64(
20/*	DICT	*dict,
21/*	const char *value)
22/*
23/*	char	*dict_file_get_error(
24/*	DICT	*dict)
25/*
26/*	void	dict_file_purge_buffers(
27/*	DICT	*dict)
28/*
29/*	const char *dict_file_lookup(
30/*	DICT	*dict)
31/* DESCRIPTION
32/*	dict_file_to_buf() reads the content of the specified files,
33/*	with names separated by CHARS_COMMA_SP, while inserting a
34/*	gratuitous newline character between files. It returns a
35/*	pointer to a buffer which is owned by the DICT, or a null
36/*	pointer in case of error.
37/*
38/*	dict_file_to_b64() invokes dict_file_to_buf() and converts
39/*	the result to base64. It returns a pointer to a buffer which
40/*	is owned by the DICT, or a null pointer in case of error.
41/*
42/*	dict_file_from_b64() converts a value from base64. It returns
43/*	a pointer to a buffer which is owned by the DICT, or a null
44/*	pointer in case of error.
45/*
46/*	dict_file_purge_buffers() disposes of dict_file-related
47/*	memory that are associated with this DICT.
48/*
49/*	dict_file_get_error() should be called only after error;
50/*	it returns a description of the problem. Storage is owned
51/*	by the caller.
52/*
53/*	dict_file_lookup() wraps the dictionary lookup method and
54/*	decodes the base64 lookup result. The dictionary must be
55/*	opened with DICT_FLAG_SRC_RHS_IS_FILE. Sets dict->error to
56/*	DICT_ERR_CONFIG if the content is invalid. Decoding is not
57/*	built into the dict->lookup() method, because that would
58/*	complicate the implementation of map nesting (inline, thash),
59/*	map composition (pipemap, unionmap), and map proxying.
60/* DIAGNOSTICS
61/*	Panic: interface violation.
62/*
63/*	In case of error the VSTRING result value is a null pointer, and
64/*	an error description can be retrieved with dict_file_get_error().
65/*	The storage is owned by the caller.
66/* LICENSE
67/* .ad
68/* .fi
69/*	The Secure Mailer license must be distributed with this software.
70/* AUTHOR(S)
71/*	Wietse Venema
72/*	Google, Inc.
73/*	111 8th Avenue
74/*	New York, NY 10011, USA
75/*--*/
76
77 /*
78  * System library.
79  */
80#include <sys_defs.h>
81#include <sys/stat.h>
82#include <string.h>
83
84 /*
85  * Utility library.
86  */
87#include <base64_code.h>
88#include <dict.h>
89#include <msg.h>
90#include <mymalloc.h>
91#include <vstream.h>
92#include <vstring.h>
93
94 /*
95  * SLMs.
96  */
97#define STR(x) vstring_str(x)
98#define LEN(x) VSTRING_LEN(x)
99
100/* dict_file_to_buf - read files into a buffer */
101
102VSTRING *dict_file_to_buf(DICT *dict, const char *pathnames)
103{
104    struct stat st;
105    VSTREAM *fp = 0;
106    ARGV   *argv;
107    char  **cpp;
108
109    /* dict_file_to_buf() postcondition: dict->file_buf exists. */
110    if (dict->file_buf == 0)
111	dict->file_buf = vstring_alloc(100);
112
113#define DICT_FILE_RETURN(retval) do { \
114	argv_free(argv); \
115	if (fp) vstream_fclose(fp); \
116	return (retval); \
117    } while (0);
118
119    argv = argv_split(pathnames, CHARS_COMMA_SP);
120    if (argv->argc == 0) {
121	vstring_sprintf(dict->file_buf, "empty pathname list: >>%s<<'",
122			pathnames);
123	DICT_FILE_RETURN(0);
124    }
125    VSTRING_RESET(dict->file_buf);
126    for (cpp = argv->argv; *cpp; cpp++) {
127	if ((fp = vstream_fopen(*cpp, O_RDONLY, 0)) == 0
128	    || fstat(vstream_fileno(fp), &st) < 0) {
129	    vstring_sprintf(dict->file_buf, "open %s: %m", *cpp);
130	    DICT_FILE_RETURN(0);
131	}
132	if (st.st_size > SSIZE_T_MAX - LEN(dict->file_buf)) {
133	    vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
134	    DICT_FILE_RETURN(0);
135	}
136	if (vstream_fread_app(fp, dict->file_buf, st.st_size) != st.st_size) {
137	    vstring_sprintf(dict->file_buf, "read %s: %m", *cpp);
138	    DICT_FILE_RETURN(0);
139	}
140	(void) vstream_fclose(fp);
141	fp = 0;
142	if (cpp[1] != 0)
143	    VSTRING_ADDCH(dict->file_buf, '\n');
144    }
145    VSTRING_TERMINATE(dict->file_buf);
146    DICT_FILE_RETURN(dict->file_buf);
147}
148
149/* dict_file_to_b64 - read files into a base64-encoded buffer */
150
151VSTRING *dict_file_to_b64(DICT *dict, const char *pathnames)
152{
153    ssize_t helper;
154
155    if (dict_file_to_buf(dict, pathnames) == 0)
156	return (0);
157    if (dict->file_b64 == 0)
158	dict->file_b64 = vstring_alloc(100);
159    helper = (LEN(dict->file_buf) + 2) / 3;
160    if (helper > SSIZE_T_MAX / 4) {
161	vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
162	return (0);
163    }
164    VSTRING_RESET(dict->file_b64);
165    VSTRING_SPACE(dict->file_b64, helper * 4);
166    return (base64_encode(dict->file_b64, STR(dict->file_buf),
167			  LEN(dict->file_buf)));
168}
169
170/* dict_file_from_b64 - convert value from base64 */
171
172VSTRING *dict_file_from_b64(DICT *dict, const char *value)
173{
174    ssize_t helper;
175    VSTRING *result;
176
177    if (dict->file_buf == 0)
178	dict->file_buf = vstring_alloc(100);
179    helper = strlen(value) / 4;
180    VSTRING_RESET(dict->file_buf);
181    VSTRING_SPACE(dict->file_buf, helper * 3);
182    result = base64_decode(dict->file_buf, value, strlen(value));
183    if (result == 0)
184	vstring_sprintf(dict->file_buf, "malformed BASE64 value: %.30s", value);
185    return (result);
186}
187
188/* dict_file_get_error - return error text */
189
190char   *dict_file_get_error(DICT *dict)
191{
192    if (dict->file_buf == 0)
193	msg_panic("dict_file_get_error: no buffer");
194    return (mystrdup(STR(dict->file_buf)));
195}
196
197/* dict_file_purge_buffers - purge file buffers */
198
199void    dict_file_purge_buffers(DICT *dict)
200{
201    if (dict->file_buf) {
202	vstring_free(dict->file_buf);
203	dict->file_buf = 0;
204    }
205    if (dict->file_b64) {
206	vstring_free(dict->file_b64);
207	dict->file_b64 = 0;
208    }
209}
210
211/* dict_file_lookup - look up and decode dictionary entry */
212
213const char *dict_file_lookup(DICT *dict, const char *key)
214{
215    const char myname[] = "dict_file_lookup";
216    const char *res;
217    VSTRING *unb64;
218    char   *err;
219
220    if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
221	msg_panic("%s: dictionary opened without DICT_FLAG_SRC_RHS_IS_FILE",
222		  myname);
223    if ((res = dict->lookup(dict, key)) == 0)
224	return (0);
225    if ((unb64 = dict_file_from_b64(dict, res)) == 0) {
226	err = dict_file_get_error(dict);
227	msg_warn("table %s:%s: key %s: %s", dict->type, dict->name, key, err);
228	myfree(err);
229	dict->error = DICT_ERR_CONFIG;
230	return (0);
231    }
232    return STR(unb64);
233}
234