From: Vsevolod Stakhov Date: Fri, 24 Apr 2015 14:18:57 +0000 (+0100) Subject: Fix pbkdf merge. X-Git-Tag: 0.9.0~160 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ed8ea0dc72b3fb10e3af42ed884ff32166a3257;p=thirdparty%2Frspamd.git Fix pbkdf merge. - Fix formatting; - Add constant time memcmp; - Fix logic of compatision; - Fix memory leak; - Other small tunes and fixes --- diff --git a/src/controller.c b/src/controller.c index 22caae2346..c7a6d4055d 100644 --- a/src/controller.c +++ b/src/controller.c @@ -64,7 +64,7 @@ #define COLOR_REJECT "#CB4B4B" #define COLOR_TOTAL "#9440ED" -#define VALID_ID 1 +#define RSPAMD_PBKDF_ID_V1 1 gpointer init_controller_worker (struct rspamd_config *cfg); void start_controller_worker (struct rspamd_worker *worker); @@ -133,181 +133,257 @@ struct rspamd_controller_session { gboolean is_spam; }; +struct rspamd_controller_pbkdf { + gint id; + guint rounds; + gsize salt_len; + gsize key_len; +}; + +static const struct rspamd_controller_pbkdf pbkdf_list[] = { + { + .id = RSPAMD_PBKDF_ID_V1, + .rounds = 16000, + .salt_len = 20, + .key_len = BLAKE2B_OUTBYTES / 2 + } +}; + static gboolean -rspamd_is_encrypted_password (const gchar * password) +rspamd_constant_memcmp (const guchar *a, const guchar *b, gsize len) { - const gchar *start, *end; - gint64 id; - gsize size; - gboolean ret = TRUE; - - if (password[0] == '$') { - /* Parse id */ - start = password + 1; - end = start; - size = 0; - while (*end != '\0' && g_ascii_isdigit (*end)) { - size++; - end++; - } - if (size) { - gchar *endptr; - id = strtoul (start, &endptr, 10); - - if ((endptr != NULL && *endptr != *end) || id != VALID_ID) { - return FALSE; - } - } else { - ret = FALSE; - } - } else { - ret = FALSE; - } - - return ret; + gsize lena, lenb, i; + gint acc = 0; + + if (len == 0) { + lena = strlen (a); + lenb = strlen (b); + + if (lena != lenb) { + return FALSE; + } + + len = lena; + } + + for (i = 0; i < len; i ++) { + acc |= a[i] ^ b[i]; + } + + return acc == 0; +} + +static gboolean +rspamd_is_encrypted_password (const gchar *password, + struct rspamd_controller_pbkdf const **pbkdf) +{ + const gchar *start, *end; + gint64 id; + gsize size; + gboolean ret = FALSE; + + if (password[0] == '$') { + /* Parse id */ + start = password + 1; + end = start; + size = 0; + + while (*end != '\0' && g_ascii_isdigit (*end)) { + size++; + end++; + } + + if (size > 0) { + gchar *endptr; + id = strtoul (start, &endptr, 10); + + if ((endptr == NULL || *endptr == *end) && id == RSPAMD_PBKDF_ID_V1) { + ret = TRUE; + *pbkdf = &pbkdf_list[0]; + } + } + } + + return ret; } static const gchar * -rspamd_encrypted_password_get_str (const gchar * password, gsize skip, gsize * length) +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; + 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 gboolean -rspamd_check_encrypted_password (const gchar * password, const gchar * check) +static gboolean rspamd_check_encrypted_password (const gchar * password, + const gchar * check, const struct rspamd_controller_pbkdf *pbkdf) { - const gchar *salt, *hash; - gsize salt_len, hash_len; - gboolean ret = TRUE; - - /* get salt */ - salt = rspamd_encrypted_password_get_str (check, 3, &salt_len); - /* get hash */ - hash = rspamd_encrypted_password_get_str (check, 3 + salt_len + 1, &hash_len); - if (salt != NULL && hash != NULL) { - guchar key[BLAKE2B_OUTBYTES]; - - /* decode salt */ - salt = rspamd_decode_base32 (salt, salt_len, &salt_len); - rspamd_cryptobox_pbkdf (password, strlen(password), salt, salt_len, key, BLAKE2B_OUTBYTES, 16000); - if (strcmp (hash, rspamd_encode_base32 (key, BLAKE2B_OUTBYTES)) != 0) { - msg_info ("incorrect or absent password has been specified"); - ret = FALSE; - } - - g_free ((void*) salt); - } - - return ret; + const gchar *salt, *hash; + gchar *salt_decoded, *key_decoded; + gsize salt_len, key_len; + gboolean ret = TRUE; + guchar *local_key; + + g_assert (pbkdf != NULL); + /* get salt */ + salt = rspamd_encrypted_password_get_str (check, 3, &salt_len); + /* get hash */ + hash = rspamd_encrypted_password_get_str (check, 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_info ("incorrect salt: %z, while %z expected", + salt_len, pbkdf->salt_len); + return FALSE; + } + + key_decoded = rspamd_decode_base32 (hash, 0, &key_len); + + if (key_decoded == NULL || key_len != pbkdf->key_len) { + /* We have some unknown salt here */ + msg_info ("incorrect key: %z, while %z expected", + key_len, pbkdf->key_len); + return FALSE; + } + + local_key = g_alloca (pbkdf->key_len); + rspamd_cryptobox_pbkdf (password, strlen (password), salt, salt_len, + local_key, pbkdf->key_len, pbkdf->rounds); + + if (!rspamd_constant_memcmp (key_decoded, local_key, pbkdf->key_len)) { + msg_info ("incorrect or absent password has been specified"); + ret = FALSE; + } + + g_free (salt_decoded); + g_free (key_decoded); + } + + return ret; } /* Check for password if it is required by configuration */ -static gboolean -rspamd_controller_check_password (struct rspamd_http_connection_entry *entry, - struct rspamd_controller_session *session, struct rspamd_http_message *msg, - gboolean is_enable) +static gboolean rspamd_controller_check_password( + struct rspamd_http_connection_entry *entry, + struct rspamd_controller_session *session, + struct rspamd_http_message *msg, gboolean is_enable) { const gchar *password, *check; struct rspamd_controller_worker_ctx *ctx = session->ctx; gboolean check_normal = TRUE, check_enable = TRUE, ret = TRUE; - + const struct rspamd_controller_pbkdf *pbkdf = NULL; + /* Access list logic */ - if (!rspamd_inet_address_get_af(session->from_addr) == AF_UNIX) { - msg_info ("allow unauthorized connection from a unix socket"); + if (!rspamd_inet_address_get_af (session->from_addr) == AF_UNIX) { + msg_info("allow unauthorized connection from a unix socket"); return TRUE; } - else if (ctx->secure_map && radix_find_compressed_addr (ctx->secure_map, - session->from_addr) != RADIX_NO_VALUE) { - msg_info ("allow unauthorized connection from a trusted IP %s", - rspamd_inet_address_to_string (session->from_addr)); + else if (ctx->secure_map + && radix_find_compressed_addr (ctx->secure_map, session->from_addr) + != RADIX_NO_VALUE) { + msg_info("allow unauthorized connection from a trusted IP %s", + rspamd_inet_address_to_string (session->from_addr)); return TRUE; } - /* Password logic */ + /* Password logic */ password = rspamd_http_message_find_header (msg, "Password"); + if (password == NULL) { - msg_info ("absent password has been specified"); + msg_info("absent password has been specified"); ret = FALSE; - } else { - if (is_enable) { - /* For privileged commands we strictly require enable password */ - if (ctx->enable_password != NULL) { - check = ctx->enable_password; - } else { - /* Use just a password (legacy mode) */ - msg_info ("using password as enable_password for a privileged command"); - check = ctx->password; - } - if (check != NULL) { - if (!rspamd_is_encrypted_password (check)) { - if (strcmp (password, check) != 0) { - ret = FALSE; - } - } else { - ret = rspamd_check_encrypted_password (password, check); - } - } else { - msg_warn ("no password to check while executing a privileged command"); - if (ctx->secure_map) { - msg_info ("deny unauthorized connection"); - ret = FALSE; - } - ret = FALSE; - } - } - else { - /* Accept both normal and enable passwords */ - if (ctx->password != NULL) { - check = ctx->password; - if (!rspamd_is_encrypted_password (check)) { - if (strcmp (password, check) != 0) { - check_normal = FALSE; - } - } else { - check_normal = rspamd_check_encrypted_password (password, check); - } - - } else { - check_normal = FALSE; - } - if (ctx->enable_password != NULL) { - check = ctx->enable_password; - if (!rspamd_is_encrypted_password (check)) { - if (strcmp (password, check) != 0) { - check_enable = FALSE; - } - } else { - check_enable = rspamd_check_encrypted_password (password, check); - } - } else { - check_enable = FALSE; - } - } - } - + } + else { + if (is_enable) { + /* For privileged commands we strictly require enable password */ + if (ctx->enable_password != NULL) { + check = ctx->enable_password; + } + else { + /* Use just a password (legacy mode) */ + msg_info( + "using password as enable_password for a privileged command"); + check = ctx->password; + } + if (check != NULL) { + if (!rspamd_is_encrypted_password (check, &pbkdf)) { + ret = rspamd_constant_memcmp (password, check, 0); + } + else { + ret = rspamd_check_encrypted_password (password, check, pbkdf); + } + } + else { + msg_warn( + "no password to check while executing a privileged command"); + if (ctx->secure_map) { + msg_info("deny unauthorized connection"); + ret = FALSE; + } + ret = FALSE; + } + } + else { + /* Accept both normal and enable passwords */ + if (ctx->password != NULL) { + check = ctx->password; + if (!rspamd_is_encrypted_password (check, &pbkdf)) { + check_normal = rspamd_constant_memcmp (password, check, 0); + } + else { + check_normal = rspamd_check_encrypted_password (password, + check, pbkdf); + } + + } + else { + check_normal = FALSE; + } + if (ctx->enable_password != NULL) { + check = ctx->enable_password; + if (!rspamd_is_encrypted_password (check, &pbkdf)) { + check_enable = rspamd_constant_memcmp (password, check, 0); + } + else { + check_enable = rspamd_check_encrypted_password (password, + check, pbkdf); + } + } + else { + check_enable = FALSE; + } + } + } + if (check_normal == FALSE && check_enable == FALSE) { - msg_info ("absent or incorrect password has been specified"); - ret = FALSE; + msg_info("absent or incorrect password has been specified"); + ret = FALSE; } - + if (!ret) { rspamd_controller_send_error (entry, 403, "Unauthorized"); }