1 /* This file is part of Mailfromd.
2    Copyright (C) 2005-2021 Sergey Poznyakoff
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 
21 #include <mailutils/mailutils.h>
22 #include <string.h>
23 #include <sysexits.h>
24 #include <sys/stat.h>
25 
26 #include "libmf.h"
27 #include "mfdb.h"
28 
29 int mf_database_mode = 0600;
30 int ignore_failed_reads_option;//FIXME;
31 
32 struct db_fmt_list {
33 	struct db_fmt_list *next;
34 	struct db_format fmt;
35 };
36 
37 static struct db_fmt_list *db_fmt_list;
38 static mu_debug_handle_t debug_handle;
39 
40 void
db_format_enumerate(dbfmt_enumerator_t fp,void * data)41 db_format_enumerate(dbfmt_enumerator_t fp, void *data)
42 {
43 	struct db_fmt_list *p;
44 
45 	for (p = db_fmt_list; p; p = p->next)
46 		fp(&p->fmt, data);
47 }
48 
49 struct db_format *
db_format_install(struct db_format * fmt)50 db_format_install(struct db_format *fmt)
51 {
52 	struct db_fmt_list *p;
53 
54 	if (!fmt)
55 		return NULL;
56 
57 	p = mu_alloc(sizeof *p);
58 	p->fmt = *fmt;
59 	mf_namefixup_register(&p->fmt.dbname, p->fmt.dbname);
60 	/* FIXME: not used so far */
61 	p->fmt.debug_handle = mu_debug_register_category(p->fmt.name);
62 	p->next = db_fmt_list;
63 	db_fmt_list = p;
64 
65 	return &p->fmt;
66 }
67 
68 struct db_format *
db_format_lookup(const char * name)69 db_format_lookup(const char *name)
70 {
71 	struct db_fmt_list *p;
72 	for (p = db_fmt_list; p; p = p->next)
73 		if (strcmp(p->fmt.name, name) == 0)
74 			return &p->fmt;
75 	return NULL;
76 }
77 
78 
79 void
db_format_setup()80 db_format_setup()
81 {
82 	debug_handle = mu_debug_register_category("db");
83 	cache_format = db_format_install(cache_format);
84 	rate_format = db_format_install(rate_format);
85 	tbf_rate_format = db_format_install(tbf_rate_format);
86 	greylist_format = db_format_install(greylist_format);
87 }
88 
89 int
mf_file_mode_to_safety_criteria(int mode)90 mf_file_mode_to_safety_criteria(int mode)
91 {
92 	int fl = 0;
93 
94 	if (!(mode & 0002))
95 		fl |= MU_FILE_SAFETY_WORLD_WRITABLE;
96 	if (!(mode & 0004))
97 		fl |= MU_FILE_SAFETY_WORLD_READABLE;
98 	if (!(mode & 0020))
99 		fl |= MU_FILE_SAFETY_GROUP_WRITABLE;
100 	if (!(mode & 0040))
101 		fl |= MU_FILE_SAFETY_GROUP_READABLE;
102 	return fl;
103 }
104 
105 mu_dbm_file_t
mf_dbm_open(char * dbname,int access)106 mf_dbm_open(char *dbname, int access)
107 {
108 	mu_dbm_file_t db;
109 	int rc;
110 
111 	rc = mu_dbm_create(dbname, &db, MU_FILE_SAFETY_LINKED_WRDIR |
112 			   MU_FILE_SAFETY_DIR_IWOTH|
113 			   mf_file_mode_to_safety_criteria(mf_database_mode));
114 	if (rc)	{
115 		mu_error(_("unable to create database %s: %s"),
116 			 dbname, mu_strerror (rc));
117 		return NULL;
118 	}
119 
120 	rc = mu_dbm_safety_check(db);
121 	if (rc && rc != ENOENT) {
122 		mu_error(_("%s fails safety check: %s"),
123 			 dbname, mu_strerror(rc));
124 		mu_dbm_destroy(&db);
125 		return NULL;
126 	}
127 
128 	rc = mu_dbm_open(db, access, mf_database_mode);
129 	if (rc) {
130 		mu_error(_("mu_dbm_open(%s) failed: %s (%s)"), dbname,
131 			 mu_strerror (rc), mu_strerror (errno));
132 		mu_dbm_destroy(&db);
133 		return NULL;
134 	}
135 	return db;
136 }
137 
138 
139 int
db_list_item(char * dbname,char * email,db_item_printer_t fun)140 db_list_item(char *dbname, char *email, db_item_printer_t fun)
141 {
142 	mu_dbm_file_t db;
143 	struct mu_dbm_datum key, contents;
144 	int rc = 1;
145 
146 	db = mf_dbm_open(dbname, MU_STREAM_READ);
147 	if (!db)
148 		return 1;
149 	memset(&contents, 0, sizeof contents);
150 	memset(&key, 0, sizeof key);
151 	key.mu_dptr = email;
152 	key.mu_dsize = strlen (email) + 1;
153 	rc = mu_dbm_fetch(db, &key, &contents);
154 	if (rc == 0) {
155 		fun(&key, &contents);
156 	} else if (rc != MU_ERR_NOENT) {
157 		mu_error(_("cannot fetch record `%s' from `%s': %s"),
158 		         email, dbname, mu_dbm_strerror(db));
159 		rc = 0;
160 	}
161 	mu_dbm_destroy(&db);
162 	return rc;
163 }
164 
165 typedef int (*db_enum_func)(struct mu_dbm_datum *key,
166 			    struct mu_dbm_datum *cnt, void *data);
167 
168 int
db_enumerate(mu_dbm_file_t db,db_enum_func fun,void * data)169 db_enumerate(mu_dbm_file_t db, db_enum_func fun, void *data)
170 {
171 	int rc = 0;
172 	int ret = 0;
173 	struct mu_dbm_datum key, contents;
174 	const char *name = "";
175 
176 	mu_dbm_get_name(db, &name);
177 
178 	memset(&key, 0, sizeof key);
179 	memset(&contents, 0, sizeof contents);
180 	for (rc = mu_dbm_firstkey(db, &key); rc == 0;
181 	     rc = mu_dbm_nextkey(db, &key)) {
182 		int res = mu_dbm_fetch(db, &key, &contents);
183 		if (res == 0) {
184 			res = fun(&key, &contents, data);
185 			if (res) {
186 				mu_dbm_datum_free(&key);
187 				break;
188 			}
189 			mu_dbm_datum_free(&contents);
190 		} else {
191  			mu_error(_("cannot fetch data `%*.*s' from `%s': %s"),
192 				 (int) key.mu_dsize, (int) key.mu_dsize,
193 				 key.mu_dptr,
194 				 name,
195 	                         mu_dbm_strerror(db));
196 			ret = 1;
197 		}
198 	}
199 	if (rc != MU_ERR_NOENT) {
200 		mu_error(_("unexpected error scanning database %s: %s"),
201 			 name, mu_dbm_strerror(db));
202 		ret = 1;
203 	}
204 	return ret;
205 }
206 
207 static int
db_list_func(struct mu_dbm_datum * key,struct mu_dbm_datum * contents,void * data)208 db_list_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
209 	     void *data)
210 {
211 	db_item_printer_t fun = data;
212 	fun(key, contents);
213 	return 0;
214 }
215 
216 int
db_list(char * dbname,db_item_printer_t fun)217 db_list(char *dbname, db_item_printer_t fun)
218 {
219 	mu_dbm_file_t db = mf_dbm_open(dbname, MU_STREAM_READ);
220 	if (!db)
221 		return 1;
222 	db_enumerate(db, db_list_func, fun);
223 	mu_dbm_destroy(&db);
224 	return 1;
225 }
226 
227 struct expire_data {
228 	db_expire_t fun;
229 	mu_opool_t pool;
230 	size_t key_count;
231 };
232 
233 static int
db_expire_func(struct mu_dbm_datum * key,struct mu_dbm_datum * contents,void * data)234 db_expire_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
235 	       void *data)
236 {
237 	struct expire_data *dp = data;
238 	if (dp->fun(contents)) {
239 		mu_opool_append(dp->pool, &key->mu_dsize,
240 				sizeof key->mu_dsize);
241 		mu_opool_append(dp->pool, key->mu_dptr, key->mu_dsize);
242 		dp->key_count++;
243 	}
244 	return 0;
245 }
246 
247 int
db_expire(char * dbname,db_expire_t fun)248 db_expire(char *dbname, db_expire_t fun)
249 {
250 	mu_dbm_file_t db;
251 	struct mu_dbm_datum key;
252 	struct expire_data ed;
253 	size_t size;
254 	char *base, *p;
255 	size_t i;
256 	int rc = 0;
257 
258 	db = mf_dbm_open(dbname, MU_STREAM_RDWR);
259 	if (!db)
260 		return 1;
261 
262 	ed.fun = fun;
263 	mu_opool_create(&ed.pool, MU_OPOOL_ENOMEMABRT);
264 	ed.key_count = 0;
265 	mu_debug(debug_handle, MU_DEBUG_TRACE0,
266 		 ("Expiring %s: mark phase", dbname));
267 	if (db_enumerate(db, db_expire_func, &ed))
268 		rc = !ignore_failed_reads_option;
269 
270 	size = 0;
271 	mu_opool_append(ed.pool, &size, sizeof size);
272 
273 	mu_debug(debug_handle, MU_DEBUG_TRACE0,
274               ("Number of keys to expire: %lu, memory used: %u",
275 	       (unsigned long) ed.key_count,
276 	       (unsigned) mu_opool_size(ed.pool)));
277 
278 	mu_debug(debug_handle, MU_DEBUG_TRACE0,
279 		 ("Expiring %s: sweep phase", dbname));
280 
281 	base = mu_opool_finish(ed.pool, NULL);
282 	memset(&key, 0, sizeof key);
283 	for (i = 0, p = base; i < ed.key_count; i++) {
284 		size = *(size_t*)p;
285 		p += sizeof(size_t);
286 
287 		mu_debug(debug_handle, MU_DEBUG_TRACE5,
288 			 ("Remove: %*.*s", (int)size, (int)size, p));
289 		key.mu_dptr = p;
290 		key.mu_dsize = size;
291 		rc = mu_dbm_delete(db, &key);
292 		if (rc && rc != MU_ERR_NOENT) {
293 			mu_error (_("cannot remove record `%*.*s' from `%s': %s"),
294 				  (int)size, (int)size,
295 				  p, dbname, mu_dbm_strerror(db));
296 			rc = 1;
297 		}
298 		p += size;
299 	}
300 
301 	mu_debug(debug_handle, MU_DEBUG_TRACE0,
302 		 ("Finished expiring %s", dbname));
303 	mu_opool_destroy(&ed.pool);
304 	mu_dbm_destroy(&db);
305 	return rc;
306 }
307 
308 int
db_delete(char * dbname,char * id)309 db_delete(char *dbname, char *id)
310 {
311         mu_dbm_file_t db;
312 	struct mu_dbm_datum key;
313 	int rc;
314 
315 	db = mf_dbm_open(dbname, MU_STREAM_RDWR);
316 	if (!db)
317 		return 1;
318 	memset(&key, 0, sizeof key);
319 	key.mu_dptr = id;
320 	key.mu_dsize = strlen (id) + 1;
321 	rc = mu_dbm_delete(db, &key);
322 	if (rc) {
323 		mu_error(_("cannot remove record `%s' from `%s': %s"), id,
324 	                 dbname, mu_dbm_strerror(db));
325 	}
326 	mu_dbm_destroy(&db);
327 	return rc;
328 }
329 
330 static char *
make_tmp_name(const char * dbname)331 make_tmp_name(const char *dbname)
332 {
333 	char *suf = strrchr(dbname, '.');
334 	char *p = strrchr(dbname, '/');
335 	size_t len;
336 	char *newname;
337 	char buf[80];
338 
339 	if (suf)
340 		len = suf - dbname;
341 	else if (p)
342 		len = p - dbname;
343 	else
344 		len = strlen(dbname);
345 	snprintf(buf, sizeof buf, "%lu", (unsigned long) getpid());
346 	newname = mu_alloc(len + 1 + strlen(buf) + 1);
347 	memcpy(newname, dbname, len);
348 	newname[len] = '.';
349 	strcpy(newname + len + 1, buf);
350 	return newname;
351 }
352 
353 struct compact_data {
354 	int rc;
355 	db_expire_t fun;
356 	mu_dbm_file_t ndb;
357 };
358 
359 static int
db_compact_func(struct mu_dbm_datum * key,struct mu_dbm_datum * contents,void * data)360 db_compact_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
361 		void *data)
362 {
363         struct compact_data *dp = data;
364 	int rc;
365 	const char *name;
366 
367 	mu_dbm_get_name(dp->ndb, &name);
368 
369 	if (!dp->fun || dp->fun(contents) == 0) {
370 		if (((char*)key->mu_dptr)[key->mu_dsize - 1]) {
371 			/* Old database format. Convert. */
372 			char *p;
373 			struct mu_dbm_datum newkey;
374 
375 			p = malloc(key->mu_dsize + 1);
376 			if (!p) {
377 				mu_error(_("not enough memory"));
378 				return 1;
379 			}
380 			memcpy(p, key->mu_dptr, key->mu_dsize);
381 			p[key->mu_dsize] = 0;
382 
383 			memset(&newkey, 0, sizeof newkey);
384 			newkey.mu_dptr = p;
385 			newkey.mu_dsize = key->mu_dsize + 1;
386 			rc = mu_dbm_store(dp->ndb, &newkey, contents, 1);
387 			if (rc) {
388 				mu_error(_("cannot insert datum `%s' into `%s': %s"),
389 					 p, name,
390 					 rc == MU_ERR_FAILURE ?
391 					  mu_dbm_strerror(dp->ndb) :
392 					  mu_strerror(rc));
393 				dp->rc = 1;
394 			}
395 			free(p);
396 		} else if ((rc = mu_dbm_store(dp->ndb, key, contents, 1))) {
397 			mu_error(_("cannot insert datum `%*.*s' into `%s': %s"),
398 			         (int)key->mu_dsize, (int)key->mu_dsize,
399 		                 (char*) key->mu_dsize,
400 	                         name,
401 				 rc == MU_ERR_FAILURE ?
402 				  mu_dbm_strerror(dp->ndb) :
403 				  mu_strerror(rc));
404 			dp->rc = 1;
405 		}
406 	}
407 	return 0;
408 }
409 
410 int
db_compact(char * dbname,db_expire_t fun)411 db_compact(char *dbname, db_expire_t fun)
412 {
413 	mu_dbm_file_t odb;
414 	char *tmpname;
415 	struct compact_data dat;
416 	const char *file_name;
417 	int flags, rc;
418 	int fd;
419 	struct stat st;
420 
421 	mu_debug(debug_handle, MU_DEBUG_TRACE0,
422 		 ("Compacting database `%s'", dbname));
423 
424 	odb = mf_dbm_open(dbname, MU_STREAM_READ);
425 	if (!odb)
426 		return 1;
427 	rc = mu_dbm_get_fd(odb, &fd, NULL);
428 	if (rc) {
429 		mu_dbm_destroy(&odb);
430 		mu_diag_funcall(MU_DIAG_ERROR, "mu_dbm_get_fd", NULL, rc);
431 		return 1;
432 	}
433 	if (fstat(fd, &st)) {
434 		mu_diag_funcall(MU_DIAG_ERROR, "fstat", NULL, errno);
435 		mu_dbm_destroy(&odb);
436 		return 1;
437 	}
438 
439 	if (getuid() != st.st_uid && getgid() != st.st_gid) {
440 		if (switch_to_privs(st.st_uid, st.st_gid, NULL))
441 			exit(EX_SOFTWARE);
442 	}
443 	umask(0777 & ~ st.st_mode);
444 	mu_dbm_get_name(odb, &file_name);
445 	tmpname = make_tmp_name(file_name);
446 
447 	mu_dbm_safety_get_flags(odb, &flags);
448 	rc = mu_dbm_create(tmpname, &dat.ndb, flags);
449 	if (rc)	{
450 		mu_error(_("unable to create database %s: %s"),
451 			 tmpname, mu_strerror (rc));
452 		return 1;
453 	}
454 	mu_dbm_safety_set_owner(dat.ndb, st.st_uid);
455 
456 	rc = mu_dbm_open(dat.ndb, MU_STREAM_CREAT, mf_database_mode);
457 	if (rc) {
458 		mu_error(_("unable to open database %s: %s"),
459 			 tmpname, mu_strerror (rc));
460 		mu_dbm_destroy(&odb);
461 		return 1;
462 	}
463 
464 	dat.rc = 0;
465 	dat.fun = fun;
466 
467 	db_enumerate(odb, db_compact_func, &dat);
468 
469 	mu_dbm_close(dat.ndb);
470 	mu_dbm_close(odb);
471 
472 	if (dat.rc == 0) {
473 		if (rename(tmpname, file_name)) {
474 			mu_error(_("cannot rename %s to %s: %s"),
475 				 tmpname, file_name, mu_strerror(errno));
476 			dat.rc = 1;
477 		}
478 	}
479 
480 	mu_dbm_destroy(&dat.ndb);
481 	mu_dbm_destroy(&odb);
482 
483 	free(tmpname);
484 	return dat.rc;
485 }
486 
487 
488