1/*	$OpenBSD: table_db.c,v 1.26 2024/05/14 13:28:08 op Exp $	*/
2
3/*
4 * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/stat.h>
20
21#include <db.h>
22#include <fcntl.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include "smtpd.h"
27#include "log.h"
28
29/* db(3) backend */
30static int table_db_config(struct table *);
31static int table_db_update(struct table *);
32static int table_db_open(struct table *);
33static void *table_db_open2(struct table *);
34static int table_db_lookup(struct table *, enum table_service, const char *, char **);
35static int table_db_fetch(struct table *, enum table_service, char **);
36static void table_db_close(struct table *);
37static void table_db_close2(void *);
38
39static char *table_db_get_entry(void *, const char *, size_t *);
40static char *table_db_get_entry_match(void *, const char *, size_t *,
41    int(*)(const char *, const char *));
42
43struct table_backend table_backend_db = {
44	.name = "db",
45	.services = K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO|
46	K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST|
47	K_STRING|K_REGEX,
48	.config = table_db_config,
49	.add = NULL,
50	.dump = NULL,
51	.open = table_db_open,
52	.update = table_db_update,
53	.close = table_db_close,
54	.lookup = table_db_lookup,
55	.fetch = table_db_fetch,
56};
57
58static struct keycmp {
59	enum table_service	service;
60	int		       (*func)(const char *, const char *);
61} keycmp[] = {
62	{ K_DOMAIN, table_domain_match },
63	{ K_NETADDR, table_netaddr_match },
64	{ K_MAILADDR, table_mailaddr_match },
65	{ K_REGEX, table_regex_match },
66};
67
68struct dbhandle {
69	DB		*db;
70	char		 pathname[PATH_MAX];
71	time_t		 mtime;
72	int		 iter;
73};
74
75static int
76table_db_config(struct table *table)
77{
78	struct dbhandle	       *handle;
79
80	handle = table_db_open2(table);
81	if (handle == NULL)
82		return 0;
83
84	table_db_close2(handle);
85	return 1;
86}
87
88static int
89table_db_update(struct table *table)
90{
91	struct dbhandle	*handle;
92
93	handle = table_db_open2(table);
94	if (handle == NULL)
95		return 0;
96
97	table_db_close2(table->t_handle);
98	table->t_handle = handle;
99	return 1;
100}
101
102static int
103table_db_open(struct table *table)
104{
105	table->t_handle = table_db_open2(table);
106	if (table->t_handle == NULL)
107		return 0;
108	return 1;
109}
110
111static void
112table_db_close(struct table *table)
113{
114	table_db_close2(table->t_handle);
115	table->t_handle = NULL;
116}
117
118static void *
119table_db_open2(struct table *table)
120{
121	struct dbhandle	       *handle;
122	struct stat		sb;
123
124	handle = xcalloc(1, sizeof *handle);
125	if (strlcpy(handle->pathname, table->t_config, sizeof handle->pathname)
126	    >= sizeof handle->pathname)
127		goto error;
128
129	if (stat(handle->pathname, &sb) == -1)
130		goto error;
131
132	handle->mtime = sb.st_mtime;
133	handle->db = dbopen(table->t_config, O_RDONLY, 0600, DB_HASH, NULL);
134	if (handle->db == NULL)
135		goto error;
136
137	return handle;
138
139error:
140	if (handle->db)
141		handle->db->close(handle->db);
142	free(handle);
143	return NULL;
144}
145
146static void
147table_db_close2(void *hdl)
148{
149	struct dbhandle	*handle = hdl;
150	handle->db->close(handle->db);
151	free(handle);
152}
153
154static int
155table_db_lookup(struct table *table, enum table_service service, const char *key,
156    char **dst)
157{
158	struct dbhandle	*handle = table->t_handle;
159	char	       *line;
160	size_t		len = 0;
161	int		ret;
162	int	       (*match)(const char *, const char *) = NULL;
163	size_t		i;
164	struct stat	sb;
165
166	if (stat(handle->pathname, &sb) == -1)
167		return -1;
168
169	/* DB has changed, close and reopen */
170	if (sb.st_mtime != handle->mtime) {
171		table_db_update(table);
172		handle = table->t_handle;
173	}
174
175	for (i = 0; i < nitems(keycmp); ++i)
176		if (keycmp[i].service == service)
177			match = keycmp[i].func;
178
179	if (match == NULL)
180		line = table_db_get_entry(handle, key, &len);
181	else
182		line = table_db_get_entry_match(handle, key, &len, match);
183	if (line == NULL)
184		return 0;
185
186	ret = 1;
187	if (dst)
188		*dst = line;
189	else
190		free(line);
191
192	return ret;
193}
194
195static int
196table_db_fetch(struct table *table, enum table_service service, char **dst)
197{
198	struct dbhandle	*handle = table->t_handle;
199	DBT dbk;
200	DBT dbd;
201	int r;
202
203	if (handle->iter == 0)
204		r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST);
205	else
206		r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT);
207	handle->iter = 1;
208	if (!r) {
209		r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST);
210		if (!r)
211			return 0;
212	}
213
214	*dst = strdup(dbk.data);
215	if (*dst == NULL)
216		return -1;
217
218	return 1;
219}
220
221
222static char *
223table_db_get_entry_match(void *hdl, const char *key, size_t *len,
224    int(*func)(const char *, const char *))
225{
226	struct dbhandle	*handle = hdl;
227	DBT dbk;
228	DBT dbd;
229	int r;
230	char *buf = NULL;
231
232	for (r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); !r;
233	     r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT)) {
234		buf = xmemdup(dbk.data, dbk.size);
235		if (func(key, buf)) {
236			*len = dbk.size;
237			return buf;
238		}
239		free(buf);
240	}
241	return NULL;
242}
243
244static char *
245table_db_get_entry(void *hdl, const char *key, size_t *len)
246{
247	struct dbhandle	*handle = hdl;
248	DBT dbk;
249	DBT dbv;
250	char pkey[LINE_MAX];
251
252	/* workaround the stupidity of the DB interface */
253	if (strlcpy(pkey, key, sizeof pkey) >= sizeof pkey)
254		fatalx("table_db_get_entry: key too long");
255	dbk.data = pkey;
256	dbk.size = strlen(pkey) + 1;
257
258	if (handle->db->get(handle->db, &dbk, &dbv, 0) != 0)
259		return NULL;
260
261	*len = dbv.size;
262
263	return xmemdup(dbv.data, dbv.size);
264}
265