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