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