From 24ff78ce9808957cd732ff7f3834e12b51d0749a Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Thu, 24 Sep 2015 18:19:58 +0100 Subject: [PATCH] Add `pw` utility to manage rspamd passwords. --- src/rspamadm/CMakeLists.txt | 2 +- src/rspamadm/commands.c | 3 + src/rspamadm/pw.c | 273 ++++++++++++++++++++++++++++++++++++ src/rspamadm/rspamadm.c | 2 +- src/rspamd.c | 4 +- src/rspamd.h | 3 + 6 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 src/rspamadm/pw.c diff --git a/src/rspamadm/CMakeLists.txt b/src/rspamadm/CMakeLists.txt index ba9814f8d..7518985a6 100644 --- a/src/rspamadm/CMakeLists.txt +++ b/src/rspamadm/CMakeLists.txt @@ -1,4 +1,4 @@ -SET(RSPAMADMSRC rspamadm.c commands.c) +SET(RSPAMADMSRC rspamadm.c commands.c pw.c) ADD_EXECUTABLE(rspamadm ${RSPAMADMSRC}) TARGET_LINK_LIBRARIES(rspamadm rspamd-server) diff --git a/src/rspamadm/commands.c b/src/rspamadm/commands.c index 26f6b02b8..c8e61b6a7 100644 --- a/src/rspamadm/commands.c +++ b/src/rspamadm/commands.c @@ -23,8 +23,11 @@ */ #include "rspamadm.h" +extern struct rspamadm_command pw_command; + const struct rspamadm_command *commands[] = { &help_command, + &pw_command, NULL }; diff --git a/src/rspamadm/pw.c b/src/rspamadm/pw.c new file mode 100644 index 000000000..c2a5ca690 --- /dev/null +++ b/src/rspamadm/pw.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2015, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "util.h" +#include "ottery.h" +#include "cryptobox.h" +#include "rspamd.h" +#include "rspamadm.h" + +static void rspamadm_pw (gint argc, gchar **argv); +static const char *rspamadm_pw_help (gboolean full_help); + +static gboolean do_encrypt = FALSE; +static gboolean do_check = FALSE; +static gboolean quiet = FALSE; +static gchar *password = NULL; + +struct rspamadm_command pw_command = { + .name = "pw", + .flags = 0, + .help = rspamadm_pw_help, + .run = rspamadm_pw +}; + +static GOptionEntry entries[] = { + {"encrypt", 'e', 0, G_OPTION_ARG_NONE, &do_encrypt, + "Encrypt password", NULL}, + {"check", 'c', 0, G_OPTION_ARG_NONE, &do_check, + "Check password", NULL}, + {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, + "Supress output", NULL}, + {"password", 'p', 0, G_OPTION_ARG_STRING, &password, + "Input password", NULL}, + {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} +}; + +static const char * +rspamadm_pw_help (gboolean full_help) +{ + const char *help_str; + + if (full_help) { + help_str = "Manipulate with passwords in rspamd\n" + "Usage: rspamadm pw [command]\n" + "Where commands are:\n" + "--encrypt: encrypt password (this is a default command)\n" + "--check: check encrypted password using encrypted password\n" + "--help: shows available options and commands"; + } + else { + help_str = "Manage rspamd passwords"; + } + + return help_str; +} + +static void +rspamadm_pw_encrypt (void) +{ + const struct rspamd_controller_pbkdf *pbkdf; + guchar *salt, *key; + gchar *encoded_salt, *encoded_key; + gsize plen; + + pbkdf = &pbkdf_list[0]; + g_assert (pbkdf != NULL); + + if (password == NULL) { + plen = 8192; + password = g_malloc0 (plen); + plen = rspamd_read_passphrase (password, plen, 0, NULL); + } + else { + plen = strlen (password); + } + + if (plen == 0) { + fprintf (stderr, "Invalid password\n"); + exit (EXIT_FAILURE); + } + + salt = g_alloca (pbkdf->salt_len); + key = g_alloca (pbkdf->key_len); + ottery_rand_bytes (salt, pbkdf->salt_len); + /* Derive key */ + rspamd_cryptobox_pbkdf (password, strlen (password), + salt, pbkdf->salt_len, key, pbkdf->key_len, pbkdf->rounds); + + encoded_salt = rspamd_encode_base32 (salt, pbkdf->salt_len); + encoded_key = rspamd_encode_base32 (key, pbkdf->key_len); + + rspamd_printf ("$%d$%s$%s\n", pbkdf->id, encoded_salt, + encoded_key); + + g_free (encoded_salt); + g_free (encoded_key); + rspamd_explicit_memzero (password, plen); + g_free (password); +} + +static const gchar * +rspamd_encrypted_password_get_str (const gchar *password, gsize skip, + gsize *length) +{ + const gchar *str, *start, *end; + gsize size; + + start = password + skip; + end = start; + size = 0; + + while (*end != '\0' && g_ascii_isalnum (*end)) { + size++; + end++; + } + + if (size) { + str = start; + *length = size; + } + else { + str = NULL; + } + + return str; +} + +static void +rspamadm_pw_check (void) +{ + const struct rspamd_controller_pbkdf *pbkdf; + GIOChannel *in; + GString *encrypted_pwd; + const gchar *salt, *hash; + guchar *salt_decoded, *key_decoded, *local_key; + gsize salt_len, key_len; + gchar test_password[8192]; + gsize plen, term = 0; + + if (password == NULL) { + encrypted_pwd = g_string_new (""); + in = g_io_channel_unix_new (STDIN_FILENO); + printf ("Enter encrypted password: "); + fflush (stdout); + g_io_channel_read_line_string (in, encrypted_pwd, &term, NULL); + + if (term != 0) { + g_string_erase (encrypted_pwd, term, encrypted_pwd->len - term); + } + g_io_channel_unref (in); + } + else { + encrypted_pwd = g_string_new (password); + } + + pbkdf = &pbkdf_list[0]; + g_assert (pbkdf != NULL); + + /* get salt */ + salt = rspamd_encrypted_password_get_str (encrypted_pwd->str, 3, &salt_len); + /* get hash */ + hash = rspamd_encrypted_password_get_str (encrypted_pwd->str, + 3 + salt_len + 1, + &key_len); + if (salt != NULL && hash != NULL) { + + /* decode salt */ + salt_decoded = rspamd_decode_base32 (salt, salt_len, &salt_len); + + if (salt_decoded == NULL || salt_len != pbkdf->salt_len) { + /* We have some unknown salt here */ + msg_err ("incorrect salt: %z, while %z expected", + salt_len, pbkdf->salt_len); + exit (EXIT_FAILURE); + } + + key_decoded = rspamd_decode_base32 (hash, key_len, &key_len); + + if (key_decoded == NULL || key_len != pbkdf->key_len) { + /* We have some unknown salt here */ + msg_err ("incorrect key: %z, while %z expected", + key_len, pbkdf->key_len); + exit (EXIT_FAILURE); + } + + plen = rspamd_read_passphrase (test_password, sizeof (test_password), + 0, NULL); + if (plen == 0) { + fprintf (stderr, "Invalid password\n"); + exit (EXIT_FAILURE); + } + + local_key = g_alloca (pbkdf->key_len); + rspamd_cryptobox_pbkdf (test_password, plen, + salt_decoded, salt_len, + local_key, pbkdf->key_len, pbkdf->rounds); + rspamd_explicit_memzero (test_password, plen); + + if (!rspamd_constant_memcmp (key_decoded, local_key, pbkdf->key_len)) { + if (!quiet) { + printf ("password incorrect\n"); + } + exit (EXIT_FAILURE); + } + + g_free (salt_decoded); + g_free (key_decoded); + g_string_free (encrypted_pwd, TRUE); + } + else { + msg_err ("bad encrypted password format"); + exit (EXIT_FAILURE); + } + + if (!quiet) { + printf ("password correct\n"); + } +} + +static void +rspamadm_pw (gint argc, gchar **argv) +{ + GOptionContext *context; + GError *error = NULL; + + context = g_option_context_new ("pw [--encrypt | --check] - manage rspamd passwords"); + g_option_context_set_summary (context, + "Summary:\n Rspamd administration utility version " + RVERSION + "\n Release id: " + RID); + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) { + fprintf (stderr, "option parsing failed: %s\n", error->message); + g_error_free (error); + exit (1); + } + + + if (!do_encrypt && !do_check) { + do_encrypt = TRUE; + } + + if (do_encrypt) { + rspamadm_pw_encrypt (); + } + else if (do_check) { + rspamadm_pw_check (); + } +} diff --git a/src/rspamadm/rspamadm.c b/src/rspamadm/rspamadm.c index 235eb0acc..9105b74b8 100644 --- a/src/rspamadm/rspamadm.c +++ b/src/rspamadm/rspamadm.c @@ -282,7 +282,7 @@ main (gint argc, gchar **argv, gchar **env) nargv[i] = g_strdup (argv[i + nargc]); } - targc = argc - nargc - 1; + targc = argc - nargc; targv = nargv; cmd->run (targc, targv); g_strfreev (nargv); diff --git a/src/rspamd.c b/src/rspamd.c index bc13f6e47..e15a598a4 100644 --- a/src/rspamd.c +++ b/src/rspamd.c @@ -150,8 +150,6 @@ static GOptionEntry entries[] = { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; -extern const struct rspamd_controller_pbkdf pbkdf_list[]; - #ifndef HAVE_SA_SIGINFO static void sig_handler (gint signo) @@ -1018,7 +1016,7 @@ do_encrypt_password (void) const struct rspamd_controller_pbkdf *pbkdf; guchar *salt, *key; gchar *encoded_salt, *encoded_key; - gchar password[BUFSIZ]; + gchar password[8192]; gsize plen; pbkdf = &pbkdf_list[0]; diff --git a/src/rspamd.h b/src/rspamd.h index 5104040a9..f23798ddc 100644 --- a/src/rspamd.h +++ b/src/rspamd.h @@ -224,6 +224,9 @@ void register_custom_controller_command (const gchar *name, gboolean privilleged, gboolean require_message); +#define RSPAMD_PBKDF_ID_V1 1 +extern const struct rspamd_controller_pbkdf pbkdf_list[]; + #endif /* -- 2.39.5