/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999-2021 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ #include "pop3d.h" #include "mailutils/opt.h" int db_list (char *input_name, char *output_name); int db_make (char *input_name, char *output_name); #define ACT_DEFAULT -1 #define ACT_CREATE 0 #define ACT_ADD 1 #define ACT_DELETE 2 #define ACT_LIST 3 #define ACT_CHPASS 4 static int permissions = 0600; static int compatibility_option = 0; static int action = ACT_DEFAULT; static char *input_name; static char *output_name; static char *user_name; static char *user_password; int action_create (void); int action_add (void); int action_delete (void); int action_list (void); int action_chpass (void); int (*ftab[]) (void) = { action_create, action_add, action_delete, action_list, action_chpass }; static void set_action (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { if (action != -1) { mu_parseopt_error (po, _("You may not specify more than one `-aldp' option")); exit (po->po_exit_error); } switch (opt->opt_short) { case 'a': action = ACT_ADD; break; case 'c': action = ACT_CREATE; break; case 'l': action = ACT_LIST; break; case 'd': action = ACT_DELETE; break; case 'm': action = ACT_CHPASS; break; default: abort (); } } static void set_permissions (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { if (mu_isdigit (arg[0])) { char *p; permissions = strtoul (arg, &p, 8); if (*p == 0) return; } mu_parseopt_error (po, _("invalid octal number: %s"), arg); exit (EX_USAGE); } static struct mu_option popauth_options[] = { MU_OPTION_GROUP (N_("Actions are:")), { "add", 'a', NULL, MU_OPTION_DEFAULT, N_("add user"), mu_c_string, NULL, set_action }, { "modify", 'm', NULL, MU_OPTION_DEFAULT, N_("modify user's record (change password)"), mu_c_string, NULL, set_action }, { "delete", 'd', NULL, MU_OPTION_DEFAULT, N_("delete user's record"), mu_c_string, NULL, set_action }, { "list", 'l', NULL, MU_OPTION_DEFAULT, N_("list the contents of DBM file"), mu_c_string, NULL, set_action }, { "create", 'c', NULL, MU_OPTION_DEFAULT, N_("create the DBM from a plaintext file"), mu_c_string, NULL, set_action }, MU_OPTION_GROUP (N_("Options are:")), { "file", 'f', N_("FILE"), MU_OPTION_DEFAULT, N_("read input from FILE (default stdin)"), mu_c_string, &input_name }, { "output", 'o', N_("FILE"), MU_OPTION_DEFAULT, N_("direct output to file"), mu_c_string, &output_name }, { "password", 'p', N_("STRING"), MU_OPTION_DEFAULT, N_("specify user's password"), mu_c_string, &user_password }, { "user", 'u', N_("USERNAME"), MU_OPTION_DEFAULT, N_("specify user name"), mu_c_string, &user_name }, { "permissions", 'P', N_("PERM"), MU_OPTION_DEFAULT, N_("force given permissions on the database"), mu_c_string, NULL, set_permissions }, { "compatibility", 0, NULL, MU_OPTION_DEFAULT, N_("compatibility mode"), mu_c_bool, &compatibility_option }, MU_OPTION_END }, *options[] = { popauth_options, NULL }; void popauth_version (struct mu_parseopt *po, mu_stream_t stream) { mu_iterator_t itr; int rc; mu_version_hook (po, stream); mu_stream_printf (stream, _("Database formats: ")); rc = mu_dbm_impl_iterator (&itr); if (rc) { mu_stream_printf (stream, "%s\n", _("unknown")); mu_error ("%s", mu_strerror (rc)); } else { int i; for (mu_iterator_first (itr), i = 0; !mu_iterator_is_done (itr); mu_iterator_next (itr), i++) { struct mu_dbm_impl *impl; mu_iterator_current (itr, (void**)&impl); if (i) mu_stream_printf (stream, ", "); mu_stream_printf (stream, "%s", impl->_dbm_name); } mu_stream_printf (stream, "\n"); mu_iterator_destroy (&itr); } mu_stream_printf (stream, _("Default database location: %s\n"), APOP_PASSFILE); exit (EX_OK); } void popauth_help_hook (struct mu_parseopt *po, mu_stream_t stream) { unsigned margin = 2; mu_stream_printf (stream, "%s", _("Default action is:\n")); mu_stream_ioctl (stream, MU_IOCTL_WORDWRAPSTREAM, MU_IOCTL_WORDWRAP_SET_MARGIN, &margin); mu_stream_printf (stream, "%s\n", _("For root: --list")); mu_stream_printf (stream, "%s\n", _("For a user: --modify --user ")); } int main (int argc, char **argv) { struct mu_parseopt po; int flags; /* Native Language Support */ MU_APP_INIT_NLS (); mu_set_program_name (argv[0]); po.po_prog_doc = N_("GNU popauth -- manage pop3 authentication database"); po.po_package_name = PACKAGE_NAME; po.po_package_url = PACKAGE_URL; po.po_bug_address = PACKAGE_BUGREPORT; po.po_extra_info = N_("General help using GNU software: "); po.po_version_hook = popauth_version; po.po_help_hook = popauth_help_hook; flags = MU_PARSEOPT_IMMEDIATE | MU_PARSEOPT_DATA | MU_PARSEOPT_PROG_DOC | MU_PARSEOPT_PACKAGE_NAME | MU_PARSEOPT_PACKAGE_URL | MU_PARSEOPT_BUG_ADDRESS | MU_PARSEOPT_EXTRA_INFO | MU_PARSEOPT_VERSION_HOOK | MU_PARSEOPT_HELP_HOOK; if (mu_parseopt (&po, argc, argv, options, flags)) exit (po.po_exit_error); if (argc > po.po_arg_start) { mu_parseopt_error (&po, _("too many arguments")); exit (po.po_exit_error); } mu_parseopt_free (&po); if (action == ACT_DEFAULT) { if (getuid () == 0) action = ACT_LIST; else action = ACT_CHPASS; } return (*ftab[action]) (); } mu_dbm_file_t open_db_file (int action, int *my_file) { mu_dbm_file_t db; struct passwd *pw; uid_t uid; int rc; int flags = 0; char *db_name = NULL; int fd; struct stat sb; int safety_flags = MU_FILE_SAFETY_ALL & ~MU_FILE_SAFETY_OWNER_MISMATCH; switch (action) { case ACT_CREATE: flags = MU_STREAM_CREAT; safety_flags = MU_FILE_SAFETY_NONE; db_name = output_name; break; case ACT_ADD: case ACT_DELETE: if (!input_name) input_name = APOP_PASSFILE; flags = MU_STREAM_RDWR; db_name = input_name; break; case ACT_LIST: if (!input_name) input_name = APOP_PASSFILE; flags = MU_STREAM_READ; safety_flags = MU_FILE_SAFETY_NONE; db_name = input_name; break; case ACT_CHPASS: if (!input_name) input_name = APOP_PASSFILE; flags = MU_STREAM_RDWR; db_name = input_name; break; default: abort (); } uid = getuid (); /* Adjust safety flags */ if (permissions & 0002) safety_flags &= ~MU_FILE_SAFETY_WORLD_WRITABLE; if (permissions & 0004) safety_flags &= ~MU_FILE_SAFETY_WORLD_READABLE; if (permissions & 0020) safety_flags &= ~MU_FILE_SAFETY_GROUP_WRITABLE; if (permissions & 0040) safety_flags &= ~MU_FILE_SAFETY_GROUP_READABLE; rc = mu_dbm_create (db_name, &db, safety_flags); if (rc) { mu_diag_output (MU_DIAG_ERROR, _("unable to create database %s: %s"), db_name, mu_strerror (rc)); exit (EX_SOFTWARE); } rc = mu_dbm_safety_check (db); if (rc && rc != ENOENT) { mu_diag_output (MU_DIAG_ERROR, _("%s fails safety check: %s"), db_name, mu_strerror (rc)); mu_dbm_destroy (&db); exit (EX_UNAVAILABLE); } rc = mu_dbm_open (db, flags, permissions); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_open", db_name, rc); exit (EX_SOFTWARE); } if (uid == 0) return db; rc = mu_dbm_get_fd (db, &fd, NULL); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_get_fd", db_name, rc); exit (EX_SOFTWARE); } if (fstat (fd, &sb)) { mu_diag_funcall (MU_DIAG_ERROR, "fstat", db_name, errno); exit (EX_SOFTWARE); } if (sb.st_uid == uid) { if (my_file) *my_file = 1; return db; } if (my_file) *my_file = 0; if (user_name) { mu_error (_("Only the file owner can use --username")); exit (EX_USAGE); } if (action != ACT_CHPASS) { mu_error (_("Operation not allowed")); exit (EX_USAGE); } pw = getpwuid (uid); if (!pw) exit (EX_OSERR); user_name = pw->pw_name; return db; } static void print_entry (mu_stream_t str, struct mu_dbm_datum const *key, struct mu_dbm_datum const *contents) { if (compatibility_option) mu_stream_printf (str, "%.*s: %.*s\n", (int) key->mu_dsize, (char*) key->mu_dptr, (int) contents->mu_dsize, (char*) contents->mu_dptr); else mu_stream_printf (str, "%.*s %.*s\n", (int) key->mu_dsize, (char*) key->mu_dptr, (int) contents->mu_dsize, (char*) contents->mu_dptr); } int action_list (void) { mu_stream_t str; mu_dbm_file_t db; struct mu_dbm_datum key, contents; int rc; db = open_db_file (ACT_LIST, NULL); if (output_name) { int rc = mu_file_stream_create (&str, output_name, MU_STREAM_WRITE|MU_STREAM_CREAT); if (rc) { mu_error (_("cannot create file %s: %s"), output_name, mu_strerror (rc)); return 1; } mu_stream_truncate (str, 0); } else { str = mu_strout; mu_stream_ref (str); } if (user_name) { memset (&key, 0, sizeof key); memset (&contents, 0, sizeof contents); key.mu_dptr = user_name; key.mu_dsize = strlen (user_name); rc = mu_dbm_fetch (db, &key, &contents); if (rc == MU_ERR_NOENT) { mu_error (_("no such user: %s"), user_name); } else if (rc) { mu_error (_("database fetch error: %s"), mu_dbm_strerror (db)); exit (EX_UNAVAILABLE); } else { print_entry (str, &key, &contents); mu_dbm_datum_free (&contents); } } else { memset (&key, 0, sizeof key); for (rc = mu_dbm_firstkey (db, &key); rc == 0; rc = mu_dbm_nextkey (db, &key)) { memset (&contents, 0, sizeof contents); rc = mu_dbm_fetch (db, &key, &contents); if (rc == 0) { print_entry (str, &key, &contents); mu_dbm_datum_free (&contents); } else { mu_error (_("database fetch error: %s"), mu_dbm_strerror (db)); exit (EX_UNAVAILABLE); } mu_dbm_datum_free (&key); } } mu_dbm_destroy (&db); return 0; } int action_create (void) { int rc; mu_stream_t in; mu_dbm_file_t db; struct mu_dbm_datum key, contents; char *buf = NULL; size_t size = 0, len; int line = 0; /* Make sure we have proper privileges if popauth is setuid */ setuid (getuid ()); if (input_name) { rc = mu_file_stream_create (&in, input_name, MU_STREAM_READ); if (rc) { mu_error (_("cannot open file %s: %s"), input_name, mu_strerror (rc)); return 1; } } else { input_name = ""; rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ); if (rc) { mu_error (_("cannot open standard input: %s"), mu_strerror (rc)); return 1; } } if (!output_name) output_name = APOP_PASSFILE; db = open_db_file (ACT_CREATE, NULL); line = 0; while ((rc = mu_stream_getline (in, &buf, &size, &len)) == 0 && len > 0) { char *str, *pass; line++; str = mu_str_stripws (buf); if (*str == 0 || *str == '#') continue; pass = mu_str_skip_class_comp (str, MU_CTYPE_SPACE); if (*pass == 0) { mu_error (_("%s:%d: malformed line"), input_name, line); continue; } /* Strip trailing semicolon, when in compatibility mode. */ if (compatibility_option && pass > str && pass[-1] == ':') pass[-1] = 0; *pass++ = 0; pass = mu_str_skip_class (pass, MU_CTYPE_SPACE); if (*pass == 0) { mu_error (_("%s:%d: malformed line"), input_name, line); continue; } memset (&key, 0, sizeof key); memset (&contents, 0, sizeof contents); key.mu_dptr = str; key.mu_dsize = strlen (str); contents.mu_dptr = pass; contents.mu_dsize = strlen (pass); rc = mu_dbm_store (db, &key, &contents, 1); if (rc) mu_error (_("%s:%d: cannot store datum: %s"), input_name, line, rc == MU_ERR_FAILURE ? mu_dbm_strerror (db) : mu_strerror (rc)); } free (buf); mu_dbm_destroy (&db); mu_stream_destroy (&in); return 0; } /*FIXME int open_io (int action, struct action_data *ap, DBM_FILE *db, int *not_owner) { int rc = check_user_perm (action, ap); if (not_owner) *not_owner = rc; if (mu_dbm_open (input_name, db, MU_STREAM_RDWR, permissions)) { mu_error (_("cannot open file %s: %s"), input_name, mu_strerror (errno)); return 1; } return 0; } */ void fill_pass (void) { if (!user_password) { char *p; mu_stream_t in, out; int rc; rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", "MU_STDIN_FD", rc); return; } rc = mu_stdio_stream_create (&out, MU_STDOUT_FD, MU_STREAM_WRITE); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", "MU_STDOUT_FD", rc); return; } while (1) { if (user_password) free (user_password); rc = mu_getpass (in, out, _("Password:"), &p); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc); exit (EX_DATAERR); } if (!p) exit (EX_DATAERR); user_password = mu_strdup (p); /* TRANSLATORS: Please try to format this string so that it has the same length as the translation of 'Password:' above */ rc = mu_getpass (in, out, _("Confirm :"), &p); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc); exit (EX_DATAERR); } if (!p) exit (EX_DATAERR); if (strcmp (user_password, p) == 0) break; mu_error (_("Passwords differ. Please retry.")); } mu_stream_destroy (&in); mu_stream_destroy (&out); } } int action_add (void) { mu_dbm_file_t db; struct mu_dbm_datum key, contents; int rc; if (!user_name) { mu_error (_("missing user name to add")); return 1; } db = open_db_file (ACT_ADD, NULL); fill_pass (); memset (&key, 0, sizeof key); memset (&contents, 0, sizeof contents); key.mu_dptr = user_name; key.mu_dsize = strlen (user_name); contents.mu_dptr = user_password; contents.mu_dsize = strlen (user_password); rc = mu_dbm_store (db, &key, &contents, 1); if (rc) mu_error (_("cannot store datum: %s"), rc == MU_ERR_FAILURE ? mu_dbm_strerror (db) : mu_strerror (rc)); mu_dbm_destroy (&db); return rc; } int action_delete (void) { mu_dbm_file_t db; struct mu_dbm_datum key; int rc; if (!user_name) { mu_error (_("missing username to delete")); return 1; } db = open_db_file (ACT_DELETE, NULL); memset (&key, 0, sizeof key); key.mu_dptr = user_name; key.mu_dsize = strlen (user_name); rc = mu_dbm_delete (db, &key); if (rc) mu_error (_("cannot remove record for %s: %s"), user_name, rc == MU_ERR_FAILURE ? mu_dbm_strerror (db) : mu_strerror (rc)); mu_dbm_destroy (&db); return rc; } int action_chpass (void) { mu_dbm_file_t db; struct mu_dbm_datum key, contents; int rc; int my_file; db = open_db_file (ACT_CHPASS, &my_file); if (!user_name) { struct passwd *pw = getpwuid (getuid ()); user_name = pw->pw_name; printf ("Changing password for %s\n", user_name); } memset (&key, 0, sizeof key); memset (&contents, 0, sizeof contents); key.mu_dptr = user_name; key.mu_dsize = strlen (user_name); rc = mu_dbm_fetch (db, &key, &contents); if (rc == MU_ERR_NOENT) { mu_error (_("no such user: %s"), user_name); return 1; } else if (rc) { mu_error (_("database fetch error: %s"), mu_dbm_strerror (db)); exit (EX_UNAVAILABLE); } if (!my_file) { char *oldpass, *p; oldpass = mu_alloc (contents.mu_dsize + 1); memcpy (oldpass, contents.mu_dptr, contents.mu_dsize); oldpass[contents.mu_dsize] = 0; p = getpass (_("Old Password:")); if (!p) return 1; if (strcmp (oldpass, p)) { mu_error (_("Sorry")); return 1; } } fill_pass (); mu_dbm_datum_free (&contents); contents.mu_dptr = user_password; contents.mu_dsize = strlen (user_password); rc = mu_dbm_store (db, &key, &contents, 1); if (rc) mu_error (_("cannot replace datum: %s"), rc == MU_ERR_FAILURE ? mu_dbm_strerror (db) : mu_strerror (rc)); mu_dbm_destroy (&db); return rc; }