1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils 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    GNU Mailutils 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "pop3d.h"
18 #include "mailutils/opt.h"
19 
20 int db_list (char *input_name, char *output_name);
21 int db_make (char *input_name, char *output_name);
22 
23 #define ACT_DEFAULT -1
24 #define ACT_CREATE  0
25 #define ACT_ADD     1
26 #define ACT_DELETE  2
27 #define ACT_LIST    3
28 #define ACT_CHPASS  4
29 
30 static int permissions = 0600;
31 static int compatibility_option = 0;
32 static int action = ACT_DEFAULT;
33 static char *input_name;
34 static char *output_name;
35 static char *user_name;
36 static char *user_password;
37 
38 int action_create (void);
39 int action_add (void);
40 int action_delete (void);
41 int action_list (void);
42 int action_chpass (void);
43 
44 int (*ftab[]) (void) = {
45   action_create,
46   action_add,
47   action_delete,
48   action_list,
49   action_chpass
50 };
51 
52 static void
set_action(struct mu_parseopt * po,struct mu_option * opt,char const * arg)53 set_action (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
54 {
55   if (action != -1)
56     {
57       mu_parseopt_error (po,
58 			 _("You may not specify more than one `-aldp' option"));
59       exit (po->po_exit_error);
60     }
61 
62   switch (opt->opt_short)
63     {
64     case 'a':
65       action = ACT_ADD;
66       break;
67 
68     case 'c':
69       action = ACT_CREATE;
70       break;
71 
72     case 'l':
73       action = ACT_LIST;
74       break;
75 
76     case 'd':
77       action = ACT_DELETE;
78       break;
79 
80     case 'm':
81       action = ACT_CHPASS;
82       break;
83 
84     default:
85       abort ();
86     }
87 }
88 
89 static void
set_permissions(struct mu_parseopt * po,struct mu_option * opt,char const * arg)90 set_permissions (struct mu_parseopt *po, struct mu_option *opt,
91 		 char const *arg)
92 {
93   if (mu_isdigit (arg[0]))
94     {
95       char *p;
96       permissions = strtoul (arg, &p, 8);
97       if (*p == 0)
98 	return;
99     }
100 
101   mu_parseopt_error (po, _("invalid octal number: %s"), arg);
102   exit (EX_USAGE);
103 }
104 
105 static struct mu_option popauth_options[] = {
106   MU_OPTION_GROUP (N_("Actions are:")),
107   { "add", 'a', NULL, MU_OPTION_DEFAULT,
108     N_("add user"),
109     mu_c_string, NULL, set_action },
110   { "modify", 'm', NULL, MU_OPTION_DEFAULT,
111     N_("modify user's record (change password)"),
112     mu_c_string, NULL, set_action },
113   { "delete", 'd', NULL, MU_OPTION_DEFAULT,
114     N_("delete user's record"),
115     mu_c_string, NULL, set_action },
116   { "list", 'l', NULL, MU_OPTION_DEFAULT,
117     N_("list the contents of DBM file"),
118     mu_c_string, NULL, set_action },
119   { "create", 'c', NULL, MU_OPTION_DEFAULT,
120     N_("create the DBM from a plaintext file"),
121     mu_c_string, NULL, set_action },
122 
123   MU_OPTION_GROUP (N_("Options are:")),
124   { "file", 'f', N_("FILE"), MU_OPTION_DEFAULT,
125     N_("read input from FILE (default stdin)"),
126     mu_c_string, &input_name },
127   { "output", 'o', N_("FILE"), MU_OPTION_DEFAULT,
128     N_("direct output to file"),
129     mu_c_string, &output_name },
130   { "password", 'p', N_("STRING"), MU_OPTION_DEFAULT,
131     N_("specify user's password"),
132     mu_c_string, &user_password },
133   { "user", 'u', N_("USERNAME"), MU_OPTION_DEFAULT,
134     N_("specify user name"),
135     mu_c_string, &user_name },
136   { "permissions", 'P', N_("PERM"), MU_OPTION_DEFAULT,
137     N_("force given permissions on the database"),
138     mu_c_string, NULL, set_permissions },
139   { "compatibility", 0, NULL, MU_OPTION_DEFAULT,
140     N_("compatibility mode"),
141     mu_c_bool, &compatibility_option },
142   MU_OPTION_END
143 }, *options[] = { popauth_options, NULL };
144 
145 void
popauth_version(struct mu_parseopt * po,mu_stream_t stream)146 popauth_version (struct mu_parseopt *po, mu_stream_t stream)
147 {
148   mu_iterator_t itr;
149   int rc;
150 
151   mu_version_hook (po, stream);
152   mu_stream_printf (stream, _("Database formats: "));
153 
154   rc = mu_dbm_impl_iterator (&itr);
155   if (rc)
156     {
157       mu_stream_printf (stream, "%s\n", _("unknown"));
158       mu_error ("%s", mu_strerror (rc));
159     }
160   else
161     {
162       int i;
163       for (mu_iterator_first (itr), i = 0; !mu_iterator_is_done (itr);
164 	   mu_iterator_next (itr), i++)
165 	{
166 	  struct mu_dbm_impl *impl;
167 
168 	  mu_iterator_current (itr, (void**)&impl);
169 	  if (i)
170 	    mu_stream_printf (stream, ", ");
171 	  mu_stream_printf (stream, "%s", impl->_dbm_name);
172 	}
173       mu_stream_printf (stream, "\n");
174       mu_iterator_destroy (&itr);
175     }
176   mu_stream_printf (stream, _("Default database location: %s\n"),
177 		    APOP_PASSFILE);
178   exit (EX_OK);
179 }
180 
181 void
popauth_help_hook(struct mu_parseopt * po,mu_stream_t stream)182 popauth_help_hook (struct mu_parseopt *po, mu_stream_t stream)
183 {
184   unsigned margin = 2;
185 
186   mu_stream_printf (stream, "%s", _("Default action is:\n"));
187   mu_stream_ioctl (stream, MU_IOCTL_WORDWRAPSTREAM,
188 		   MU_IOCTL_WORDWRAP_SET_MARGIN, &margin);
189   mu_stream_printf (stream, "%s\n", _("For root: --list"));
190   mu_stream_printf (stream, "%s\n",
191 		    _("For a user: --modify --user <username>"));
192 }
193 
194 int
main(int argc,char ** argv)195 main (int argc, char **argv)
196 {
197   struct mu_parseopt po;
198   int flags;
199 
200   /* Native Language Support */
201   MU_APP_INIT_NLS ();
202 
203   mu_set_program_name (argv[0]);
204 
205   po.po_prog_doc = N_("GNU popauth -- manage pop3 authentication database");
206   po.po_package_name = PACKAGE_NAME;
207   po.po_package_url = PACKAGE_URL;
208   po.po_bug_address = PACKAGE_BUGREPORT;
209   po.po_extra_info =
210     N_("General help using GNU software: <http://www.gnu.org/gethelp/>");
211   po.po_version_hook = popauth_version;
212   po.po_help_hook = popauth_help_hook;
213 
214   flags = MU_PARSEOPT_IMMEDIATE
215     | MU_PARSEOPT_DATA
216     | MU_PARSEOPT_PROG_DOC
217     | MU_PARSEOPT_PACKAGE_NAME
218     | MU_PARSEOPT_PACKAGE_URL
219     | MU_PARSEOPT_BUG_ADDRESS
220     | MU_PARSEOPT_EXTRA_INFO
221     | MU_PARSEOPT_VERSION_HOOK
222     | MU_PARSEOPT_HELP_HOOK;
223 
224   if (mu_parseopt (&po, argc, argv, options, flags))
225     exit (po.po_exit_error);
226 
227   if (argc > po.po_arg_start)
228     {
229       mu_parseopt_error (&po, _("too many arguments"));
230       exit (po.po_exit_error);
231     }
232   mu_parseopt_free (&po);
233 
234   if (action == ACT_DEFAULT)
235     {
236       if (getuid () == 0)
237 	action = ACT_LIST;
238       else
239 	action = ACT_CHPASS;
240     }
241   return (*ftab[action]) ();
242 }
243 
244 mu_dbm_file_t
open_db_file(int action,int * my_file)245 open_db_file (int action, int *my_file)
246 {
247   mu_dbm_file_t db;
248   struct passwd *pw;
249   uid_t uid;
250   int rc;
251   int flags = 0;
252   char *db_name = NULL;
253   int fd;
254   struct stat sb;
255   int safety_flags = MU_FILE_SAFETY_ALL & ~MU_FILE_SAFETY_OWNER_MISMATCH;
256 
257   switch (action)
258     {
259     case ACT_CREATE:
260       flags = MU_STREAM_CREAT;
261       safety_flags = MU_FILE_SAFETY_NONE;
262       db_name = output_name;
263       break;
264 
265     case ACT_ADD:
266     case ACT_DELETE:
267       if (!input_name)
268 	input_name = APOP_PASSFILE;
269       flags = MU_STREAM_RDWR;
270       db_name = input_name;
271       break;
272 
273     case ACT_LIST:
274       if (!input_name)
275 	input_name = APOP_PASSFILE;
276       flags = MU_STREAM_READ;
277       safety_flags = MU_FILE_SAFETY_NONE;
278       db_name = input_name;
279       break;
280 
281     case ACT_CHPASS:
282       if (!input_name)
283 	input_name = APOP_PASSFILE;
284       flags = MU_STREAM_RDWR;
285       db_name = input_name;
286       break;
287 
288     default:
289       abort ();
290     }
291 
292   uid = getuid ();
293 
294   /* Adjust safety flags */
295   if (permissions & 0002)
296     safety_flags &= ~MU_FILE_SAFETY_WORLD_WRITABLE;
297   if (permissions & 0004)
298     safety_flags &= ~MU_FILE_SAFETY_WORLD_READABLE;
299   if (permissions & 0020)
300     safety_flags &= ~MU_FILE_SAFETY_GROUP_WRITABLE;
301   if (permissions & 0040)
302     safety_flags &= ~MU_FILE_SAFETY_GROUP_READABLE;
303 
304   rc = mu_dbm_create (db_name, &db, safety_flags);
305   if (rc)
306     {
307       mu_diag_output (MU_DIAG_ERROR, _("unable to create database %s: %s"),
308 		      db_name, mu_strerror (rc));
309       exit (EX_SOFTWARE);
310     }
311 
312   rc = mu_dbm_safety_check (db);
313   if (rc && rc != ENOENT)
314     {
315       mu_diag_output (MU_DIAG_ERROR,
316 		      _("%s fails safety check: %s"),
317 		      db_name, mu_strerror (rc));
318       mu_dbm_destroy (&db);
319       exit (EX_UNAVAILABLE);
320     }
321 
322   rc = mu_dbm_open (db, flags, permissions);
323   if (rc)
324     {
325       mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_open",
326 		       db_name, rc);
327       exit (EX_SOFTWARE);
328     }
329 
330   if (uid == 0)
331     return db;
332 
333   rc = mu_dbm_get_fd (db, &fd, NULL);
334   if (rc)
335     {
336       mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_get_fd",
337 		       db_name, rc);
338       exit (EX_SOFTWARE);
339     }
340 
341   if (fstat (fd, &sb))
342     {
343       mu_diag_funcall (MU_DIAG_ERROR, "fstat",
344 		       db_name, errno);
345       exit (EX_SOFTWARE);
346     }
347 
348   if (sb.st_uid == uid)
349     {
350       if (my_file)
351 	*my_file = 1;
352       return db;
353     }
354   if (my_file)
355     *my_file = 0;
356 
357   if (user_name)
358     {
359       mu_error (_("Only the file owner can use --username"));
360       exit (EX_USAGE);
361     }
362 
363   if (action != ACT_CHPASS)
364     {
365       mu_error (_("Operation not allowed"));
366       exit (EX_USAGE);
367     }
368   pw = getpwuid (uid);
369   if (!pw)
370     exit (EX_OSERR);
371   user_name = pw->pw_name;
372   return db;
373 }
374 
375 static void
print_entry(mu_stream_t str,struct mu_dbm_datum const * key,struct mu_dbm_datum const * contents)376 print_entry (mu_stream_t str, struct mu_dbm_datum const *key,
377 	     struct mu_dbm_datum const *contents)
378 {
379   if (compatibility_option)
380     mu_stream_printf (str, "%.*s: %.*s\n",
381 		      (int) key->mu_dsize,
382 		      (char*) key->mu_dptr,
383 		      (int) contents->mu_dsize,
384 		      (char*) contents->mu_dptr);
385   else
386     mu_stream_printf (str, "%.*s %.*s\n",
387 		      (int) key->mu_dsize,
388 		      (char*) key->mu_dptr,
389 		      (int) contents->mu_dsize,
390 		      (char*) contents->mu_dptr);
391 }
392 
393 int
action_list(void)394 action_list (void)
395 {
396   mu_stream_t str;
397   mu_dbm_file_t db;
398   struct mu_dbm_datum key, contents;
399   int rc;
400 
401   db = open_db_file (ACT_LIST, NULL);
402 
403   if (output_name)
404     {
405       int rc = mu_file_stream_create (&str, output_name,
406 				      MU_STREAM_WRITE|MU_STREAM_CREAT);
407       if (rc)
408 	{
409 	  mu_error (_("cannot create file %s: %s"),
410 		    output_name, mu_strerror (rc));
411 	  return 1;
412 	}
413       mu_stream_truncate (str, 0);
414     }
415   else
416     {
417       str = mu_strout;
418       mu_stream_ref (str);
419     }
420 
421   if (user_name)
422     {
423       memset (&key, 0, sizeof key);
424       memset (&contents, 0, sizeof contents);
425       key.mu_dptr = user_name;
426       key.mu_dsize = strlen (user_name);
427       rc = mu_dbm_fetch (db, &key, &contents);
428       if (rc == MU_ERR_NOENT)
429 	{
430 	  mu_error (_("no such user: %s"), user_name);
431 	}
432       else if (rc)
433 	{
434 	  mu_error (_("database fetch error: %s"), mu_dbm_strerror (db));
435 	  exit (EX_UNAVAILABLE);
436 	}
437       else
438 	{
439 	  print_entry (str, &key, &contents);
440 	  mu_dbm_datum_free (&contents);
441 	}
442     }
443   else
444     {
445       memset (&key, 0, sizeof key);
446       for (rc = mu_dbm_firstkey (db, &key); rc == 0;
447 	   rc = mu_dbm_nextkey (db, &key))
448 	{
449 	  memset (&contents, 0, sizeof contents);
450 	  rc = mu_dbm_fetch (db, &key, &contents);
451 	  if (rc == 0)
452 	    {
453 	      print_entry (str, &key, &contents);
454 	      mu_dbm_datum_free (&contents);
455 	    }
456 	  else
457 	    {
458 	      mu_error (_("database fetch error: %s"), mu_dbm_strerror (db));
459 	      exit (EX_UNAVAILABLE);
460 	    }
461 	  mu_dbm_datum_free (&key);
462 	}
463     }
464 
465   mu_dbm_destroy (&db);
466   return 0;
467 }
468 
469 int
action_create(void)470 action_create (void)
471 {
472   int rc;
473   mu_stream_t in;
474   mu_dbm_file_t db;
475   struct mu_dbm_datum key, contents;
476   char *buf = NULL;
477   size_t size = 0, len;
478   int line = 0;
479 
480   /* Make sure we have proper privileges if popauth is setuid */
481   setuid (getuid ());
482 
483   if (input_name)
484     {
485       rc = mu_file_stream_create (&in, input_name, MU_STREAM_READ);
486       if (rc)
487 	{
488 	  mu_error (_("cannot open file %s: %s"),
489 		    input_name, mu_strerror (rc));
490 	  return 1;
491 	}
492     }
493   else
494     {
495       input_name = "";
496       rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ);
497       if (rc)
498 	{
499 	  mu_error (_("cannot open standard input: %s"),
500 		    mu_strerror (rc));
501 	  return 1;
502 	}
503     }
504 
505   if (!output_name)
506     output_name = APOP_PASSFILE;
507 
508   db = open_db_file (ACT_CREATE, NULL);
509 
510   line = 0;
511   while ((rc = mu_stream_getline (in, &buf, &size, &len)) == 0
512 	 && len > 0)
513     {
514       char *str, *pass;
515 
516       line++;
517       str = mu_str_stripws (buf);
518       if (*str == 0 || *str == '#')
519 	continue;
520       pass = mu_str_skip_class_comp (str, MU_CTYPE_SPACE);
521       if (*pass == 0)
522 	{
523 	  mu_error (_("%s:%d: malformed line"), input_name, line);
524 	  continue;
525 	}
526       /* Strip trailing semicolon, when in compatibility mode. */
527       if (compatibility_option && pass > str && pass[-1] == ':')
528 	pass[-1] = 0;
529       *pass++ = 0;
530       pass = mu_str_skip_class (pass, MU_CTYPE_SPACE);
531       if (*pass == 0)
532 	{
533 	  mu_error (_("%s:%d: malformed line"), input_name, line);
534 	  continue;
535 	}
536 
537       memset (&key, 0, sizeof key);
538       memset (&contents, 0, sizeof contents);
539       key.mu_dptr = str;
540       key.mu_dsize = strlen (str);
541       contents.mu_dptr = pass;
542       contents.mu_dsize = strlen (pass);
543 
544       rc = mu_dbm_store (db, &key, &contents, 1);
545       if (rc)
546 	mu_error (_("%s:%d: cannot store datum: %s"),
547 		  input_name, line,
548 		  rc == MU_ERR_FAILURE ?
549 		     mu_dbm_strerror (db) : mu_strerror (rc));
550     }
551   free (buf);
552   mu_dbm_destroy (&db);
553   mu_stream_destroy (&in);
554   return 0;
555 }
556 
557 /*FIXME
558 int
559 open_io (int action, struct action_data *ap, DBM_FILE *db, int *not_owner)
560 {
561   int rc = check_user_perm (action, ap);
562   if (not_owner)
563     *not_owner = rc;
564   if (mu_dbm_open (input_name, db, MU_STREAM_RDWR, permissions))
565     {
566       mu_error (_("cannot open file %s: %s"),
567 		input_name, mu_strerror (errno));
568       return 1;
569     }
570   return 0;
571 }
572 */
573 
574 void
fill_pass(void)575 fill_pass (void)
576 {
577   if (!user_password)
578     {
579       char *p;
580       mu_stream_t in, out;
581       int rc;
582 
583       rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ);
584       if (rc)
585 	{
586 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create",
587 			   "MU_STDIN_FD", rc);
588 	  return;
589 	}
590 
591       rc = mu_stdio_stream_create (&out, MU_STDOUT_FD, MU_STREAM_WRITE);
592       if (rc)
593 	{
594 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create",
595 			   "MU_STDOUT_FD", rc);
596 	  return;
597 	}
598 
599       while (1)
600 	{
601 	  if (user_password)
602 	    free (user_password);
603 	  rc = mu_getpass (in, out, _("Password:"), &p);
604 	  if (rc)
605 	    {
606 	      mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc);
607 	      exit (EX_DATAERR);
608 	    }
609 
610 	  if (!p)
611 	    exit (EX_DATAERR);
612 
613 	  user_password = mu_strdup (p);
614 	  /* TRANSLATORS: Please try to format this string so that it has
615 	     the same length as the translation of 'Password:' above */
616 	  rc = mu_getpass (in, out, _("Confirm :"), &p);
617 	  if (rc)
618 	    {
619 	      mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc);
620 	      exit (EX_DATAERR);
621 	    }
622 
623 	  if (!p)
624 	    exit (EX_DATAERR);
625 	  if (strcmp (user_password, p) == 0)
626 	    break;
627 	  mu_error (_("Passwords differ. Please retry."));
628 	}
629       mu_stream_destroy (&in);
630       mu_stream_destroy (&out);
631     }
632 }
633 
634 int
action_add(void)635 action_add (void)
636 {
637   mu_dbm_file_t db;
638   struct mu_dbm_datum key, contents;
639   int rc;
640 
641   if (!user_name)
642     {
643       mu_error (_("missing user name to add"));
644       return 1;
645     }
646 
647   db = open_db_file (ACT_ADD, NULL);
648 
649   fill_pass ();
650 
651   memset (&key, 0, sizeof key);
652   memset (&contents, 0, sizeof contents);
653   key.mu_dptr = user_name;
654   key.mu_dsize = strlen (user_name);
655   contents.mu_dptr = user_password;
656   contents.mu_dsize = strlen (user_password);
657 
658   rc = mu_dbm_store (db, &key, &contents, 1);
659   if (rc)
660     mu_error (_("cannot store datum: %s"),
661 	      rc == MU_ERR_FAILURE ?
662 		     mu_dbm_strerror (db) : mu_strerror (rc));
663 
664   mu_dbm_destroy (&db);
665   return rc;
666 }
667 
668 int
action_delete(void)669 action_delete (void)
670 {
671   mu_dbm_file_t db;
672   struct mu_dbm_datum key;
673   int rc;
674 
675   if (!user_name)
676     {
677       mu_error (_("missing username to delete"));
678       return 1;
679     }
680 
681   db = open_db_file (ACT_DELETE, NULL);
682 
683   memset (&key, 0, sizeof key);
684   key.mu_dptr = user_name;
685   key.mu_dsize = strlen (user_name);
686 
687   rc = mu_dbm_delete (db, &key);
688   if (rc)
689     mu_error (_("cannot remove record for %s: %s"),
690 	      user_name,
691 	      rc == MU_ERR_FAILURE ?
692 		     mu_dbm_strerror (db) : mu_strerror (rc));
693 
694   mu_dbm_destroy (&db);
695   return rc;
696 }
697 
698 int
action_chpass(void)699 action_chpass (void)
700 {
701   mu_dbm_file_t db;
702   struct mu_dbm_datum key, contents;
703   int rc;
704   int my_file;
705 
706   db = open_db_file (ACT_CHPASS, &my_file);
707 
708   if (!user_name)
709     {
710       struct passwd *pw = getpwuid (getuid ());
711       user_name = pw->pw_name;
712       printf ("Changing password for %s\n", user_name);
713     }
714 
715   memset (&key, 0, sizeof key);
716   memset (&contents, 0, sizeof contents);
717 
718   key.mu_dptr = user_name;
719   key.mu_dsize = strlen (user_name);
720   rc = mu_dbm_fetch (db, &key, &contents);
721   if (rc == MU_ERR_NOENT)
722     {
723       mu_error (_("no such user: %s"), user_name);
724       return 1;
725     }
726   else if (rc)
727     {
728       mu_error (_("database fetch error: %s"), mu_dbm_strerror (db));
729       exit (EX_UNAVAILABLE);
730     }
731 
732   if (!my_file)
733     {
734       char *oldpass, *p;
735 
736       oldpass = mu_alloc (contents.mu_dsize + 1);
737       memcpy (oldpass, contents.mu_dptr, contents.mu_dsize);
738       oldpass[contents.mu_dsize] = 0;
739       p = getpass (_("Old Password:"));
740       if (!p)
741 	return 1;
742       if (strcmp (oldpass, p))
743 	{
744 	  mu_error (_("Sorry"));
745 	  return 1;
746 	}
747     }
748 
749   fill_pass ();
750 
751   mu_dbm_datum_free (&contents);
752   contents.mu_dptr = user_password;
753   contents.mu_dsize = strlen (user_password);
754   rc = mu_dbm_store (db, &key, &contents, 1);
755   if (rc)
756     mu_error (_("cannot replace datum: %s"),
757 	      rc == MU_ERR_FAILURE ?
758 		     mu_dbm_strerror (db) : mu_strerror (rc));
759 
760   mu_dbm_destroy (&db);
761   return rc;
762 }
763 
764