xref: /openbsd/usr.sbin/smtpd/table_static.c (revision 73471bf0)
1 /*	$OpenBSD: table_static.c,v 1.33 2021/06/14 17:58:16 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2013 Eric Faurot <eric@openbsd.org>
5  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <ctype.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "smtpd.h"
26 #include "log.h"
27 
28 struct table_static_priv {
29 	int		 type;
30 	struct dict	 dict;
31 	void		*iter;
32 };
33 
34 /* static backend */
35 static int table_static_config(struct table *);
36 static int table_static_add(struct table *, const char *, const char *);
37 static void table_static_dump(struct table *);
38 static int table_static_update(struct table *);
39 static int table_static_open(struct table *);
40 static int table_static_lookup(struct table *, enum table_service, const char *,
41     char **);
42 static int table_static_fetch(struct table *, enum table_service, char **);
43 static void table_static_close(struct table *);
44 
45 struct table_backend table_backend_static = {
46 	"static",
47 	K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO|
48 	K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST|
49 	K_STRING|K_REGEX,
50 	table_static_config,
51 	table_static_add,
52 	table_static_dump,
53 	table_static_open,
54 	table_static_update,
55 	table_static_close,
56 	table_static_lookup,
57 	table_static_fetch
58 };
59 
60 static struct keycmp {
61 	enum table_service	service;
62 	int		       (*func)(const char *, const char *);
63 } keycmp[] = {
64 	{ K_DOMAIN, table_domain_match },
65 	{ K_NETADDR, table_netaddr_match },
66 	{ K_MAILADDR, table_mailaddr_match },
67 	{ K_REGEX, table_regex_match },
68 };
69 
70 
71 static void
72 table_static_priv_free(struct table_static_priv *priv)
73 {
74 	void *p;
75 
76 	while (dict_poproot(&priv->dict, (void **)&p))
77 		if (p != priv)
78 			free(p);
79 	free(priv);
80 }
81 
82 static int
83 table_static_priv_add(struct table_static_priv *priv, const char *key, const char *val)
84 {
85 	char lkey[1024];
86 	void *old, *new = NULL;
87 
88 	if (!lowercase(lkey, key, sizeof lkey)) {
89 		errno = ENAMETOOLONG;
90 		return (-1);
91 	}
92 
93 	if (val) {
94 		new = strdup(val);
95 		if (new == NULL)
96 			return (-1);
97 	}
98 
99 	/* use priv if value is null, so we can detect duplicate entries */
100 	old = dict_set(&priv->dict, lkey, new ? new : priv);
101 	if (old) {
102 		if (old != priv)
103 			free(old);
104 		return (1);
105 	}
106 
107 	return (0);
108 }
109 
110 static int
111 table_static_priv_load(struct table_static_priv *priv, const char *path)
112 {
113 	FILE	*fp;
114 	char	*buf = NULL, *p;
115 	int	 lineno = 0;
116 	size_t	 sz = 0;
117 	ssize_t	 flen;
118 	char	*keyp;
119 	char	*valp;
120 	int	 ret = 0;
121 
122 	if ((fp = fopen(path, "r")) == NULL) {
123 		log_warn("%s: fopen", path);
124 		return 0;
125 	}
126 
127 	while ((flen = getline(&buf, &sz, fp)) != -1) {
128 		lineno++;
129 		if (buf[flen - 1] == '\n')
130 			buf[--flen] = '\0';
131 
132 		keyp = buf;
133 		while (isspace((unsigned char)*keyp)) {
134 			++keyp;
135 			--flen;
136 		}
137 		if (*keyp == '\0')
138 			continue;
139 		while (isspace((unsigned char)keyp[flen - 1]))
140 			keyp[--flen] = '\0';
141 		if (*keyp == '#') {
142 			if (priv->type == T_NONE) {
143 				keyp++;
144 				while (isspace((unsigned char)*keyp))
145 					++keyp;
146 				if (!strcmp(keyp, "@list"))
147 					priv->type = T_LIST;
148 			}
149 			continue;
150 		}
151 
152 		if (priv->type == T_NONE) {
153 			for (p = keyp; *p; p++) {
154 				if (*p == ' ' || *p == '\t' || *p == ':') {
155 					priv->type = T_HASH;
156 					break;
157 				}
158 			}
159 			if (priv->type == T_NONE)
160 				priv->type = T_LIST;
161 		}
162 
163 		if (priv->type == T_LIST) {
164 			table_static_priv_add(priv, keyp, NULL);
165 			continue;
166 		}
167 
168 		/* T_HASH */
169 		valp = keyp;
170 		strsep(&valp, " \t:");
171 		if (valp) {
172 			while (*valp) {
173 				if (!isspace((unsigned char)*valp) &&
174 				    !(*valp == ':' &&
175 				    isspace((unsigned char)*(valp + 1))))
176 					break;
177 				++valp;
178 			}
179 			if (*valp == '\0')
180 				valp = NULL;
181 		}
182 		if (valp == NULL) {
183 			log_warnx("%s: invalid map entry line %d",
184 			    path, lineno);
185 			goto end;
186 		}
187 
188 		table_static_priv_add(priv, keyp, valp);
189 	}
190 
191 	if (ferror(fp)) {
192 		log_warn("%s: getline", path);
193 		goto end;
194 	}
195 
196 	/* Accept empty alias files; treat them as hashes */
197 	if (priv->type == T_NONE)
198 		priv->type = T_HASH;
199 
200 	ret = 1;
201 end:
202 	free(buf);
203 	fclose(fp);
204 	return ret;
205 }
206 
207 static int
208 table_static_config(struct table *t)
209 {
210 	struct table_static_priv *priv, *old;
211 
212 	/* already up, and no config file? ok */
213 	if (t->t_handle && *t->t_config == '\0')
214 		return 1;
215 
216 	/* new config */
217 	priv = calloc(1, sizeof(*priv));
218 	if (priv == NULL)
219 		return 0;
220 	priv->type = t->t_type;
221 	dict_init(&priv->dict);
222 
223 	if (*t->t_config) {
224 		/* load the config file */
225 		if (table_static_priv_load(priv, t->t_config) == 0) {
226 			table_static_priv_free(priv);
227 			return 0;
228 		}
229 	}
230 
231 	if ((old = t->t_handle))
232 		table_static_priv_free(old);
233 	t->t_handle = priv;
234 	t->t_type = priv->type;
235 
236 	return 1;
237 }
238 
239 static int
240 table_static_add(struct table *table, const char *key, const char *val)
241 {
242 	struct table_static_priv *priv = table->t_handle;
243 	int r;
244 
245 	/* cannot add to a table read from a file */
246 	if (*table->t_config)
247 		return 0;
248 
249 	if (table->t_type == T_NONE)
250 		table->t_type = val ? T_HASH : T_LIST;
251 	else if (table->t_type == T_LIST && val)
252 		return 0;
253 	else if (table->t_type == T_HASH && val == NULL)
254 		return 0;
255 
256 	if (priv == NULL) {
257 		if (table_static_config(table) == 0)
258 			return 0;
259 		priv = table->t_handle;
260 	}
261 
262 	r = table_static_priv_add(priv, key, val);
263 	if (r == -1)
264 		return 0;
265 	return 1;
266 }
267 
268 static void
269 table_static_dump(struct table *table)
270 {
271 	struct table_static_priv *priv = table->t_handle;
272 	const char *key;
273 	char *value;
274 	void *iter;
275 
276 	iter = NULL;
277 	while (dict_iter(&priv->dict, &iter, &key, (void**)&value)) {
278 		if (value && (void*)value != (void*)priv)
279 			log_debug("	\"%s\" -> \"%s\"", key, value);
280 		else
281 			log_debug("	\"%s\"", key);
282 	}
283 }
284 
285 static int
286 table_static_update(struct table *table)
287 {
288 	if (table_static_config(table) == 1) {
289 		log_info("info: Table \"%s\" successfully updated", table->t_name);
290 		return 1;
291 	}
292 
293 	log_info("info: Failed to update table \"%s\"", table->t_name);
294 	return 0;
295 }
296 
297 static int
298 table_static_open(struct table *table)
299 {
300 	if (table->t_handle == NULL)
301 		return table_static_config(table);
302 	return 1;
303 }
304 
305 static void
306 table_static_close(struct table *table)
307 {
308 	struct table_static_priv *priv = table->t_handle;
309 
310 	if (priv)
311 		table_static_priv_free(priv);
312 	table->t_handle = NULL;
313 }
314 
315 static int
316 table_static_lookup(struct table *table, enum table_service service, const char *key,
317     char **dst)
318 {
319 	struct table_static_priv *priv = table->t_handle;
320 	char	       *line;
321 	int		ret;
322 	int	       (*match)(const char *, const char *) = NULL;
323 	size_t		i;
324 	void	       *iter;
325 	const char     *k;
326 	char	       *v;
327 
328 	for (i = 0; i < nitems(keycmp); ++i)
329 		if (keycmp[i].service == service)
330 			match = keycmp[i].func;
331 
332 	line = NULL;
333 	iter = NULL;
334 	ret = 0;
335 	while (dict_iter(&priv->dict, &iter, &k, (void **)&v)) {
336 		if (match) {
337 			if (match(key, k)) {
338 				line = v;
339 				ret = 1;
340 			}
341 		}
342 		else {
343 			if (strcmp(key, k) == 0) {
344 				line = v;
345 				ret = 1;
346 			}
347 		}
348 		if (ret)
349 			break;
350 	}
351 
352 	if (dst == NULL)
353 		return ret ? 1 : 0;
354 
355 	if (ret == 0)
356 		return 0;
357 
358 	*dst = strdup(line);
359 	if (*dst == NULL)
360 		return -1;
361 
362 	return 1;
363 }
364 
365 static int
366 table_static_fetch(struct table *t, enum table_service service, char **dst)
367 {
368 	struct table_static_priv *priv = t->t_handle;
369 	const char *k;
370 
371 	if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) {
372 		priv->iter = NULL;
373 		if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL))
374 			return 0;
375 	}
376 
377 	*dst = strdup(k);
378 	if (*dst == NULL)
379 		return -1;
380 
381 	return 1;
382 }
383