Hi there,

I'm a(n) (almost) happy Jabberd2 user :-).  I say "almost" because
unfortunately it lacks any kind of security for storing passwords when
using the SQLite3 storage backend.

While taking a look at your website and resources, I found this:

<https://github.com/jabberd2/jabberd2/issues/27>

While I agree with "smokku" there, I also think this kind of issue
should not be overlooked, and the fix should not be postponed.  So I
went ahead and fixed it.

The patch is indeed a copy-and-paste of the code which adds this
security in other backends.  I also made a little cleanup in the
"check_password" function of the SQLite3 backend (obvious).  I tested
the patch by creating several users and logging into the server with
them afterwards.  Everything succeeded.

The ChangeLog seems abandoned, so I didn't bother updating it (I can do
if you want).  I also wasn't sure if I should update the NEWS file or
not, so I left it untouched too.  And please let me know if there is
something missing in my approach.

Otherwise, I'd like to hear opinions (or acceptance, if you will!).

Thanks,

-- 
Sergio

diff --git a/etc/c2s.xml.dist.in b/etc/c2s.xml.dist.in
index 6626b43..374ba26 100644
--- a/etc/c2s.xml.dist.in
+++ b/etc/c2s.xml.dist.in
@@ -430,6 +430,23 @@
 
       <!-- SQLite busy-timeout in milliseconds. -->
       <busy-timeout>2000</busy-timeout>
+
+      <!-- Passwords in DB may be stored in plain or hashed format -->
+      <!-- NOTE: If you are using hashed passwords, the only auth
+                 method that will work is PLAIN.
+                 Make sure that you disabled others in 'mechanisms'
+                 sections of the config file. -->
+      <password_type>
+        <!-- only one may be enabled here -->
+        <plaintext/>
+        <!-- use crypt(3)ed passwords
+        <crypt/>
+        -->
+        <!-- use A1HASH passwords
+                    This stores the MD5 digest of user:realm:password in the 
database
+        <a1hash/>
+        -->
+      </password_type>
     </sqlite>
 
     <!-- MySQL module configuration -->
diff --git a/storage/authreg_sqlite.c b/storage/authreg_sqlite.c
index 0bbdd76..2bea9be 100644
--- a/storage/authreg_sqlite.c
+++ b/storage/authreg_sqlite.c
@@ -29,9 +29,44 @@
  * to the Jabberd project.
  */
 
+#define _XOPEN_SOURCE 500
 #include "c2s.h"
 #include <sqlite3.h>
 
+/* Windows does not have the crypt() function, let's take DES_crypt from 
OpenSSL instead */
+#if defined(HAVE_OPENSSL_CRYPTO_H) && defined(_WIN32)
+#include <openssl/des.h>
+#define crypt DES_crypt
+#define HAVE_CRYPT 1
+#else
+#ifdef HAVE_CRYPT
+#include <unistd.h>
+#endif
+#endif
+
+#ifdef HAVE_SSL
+/* We use OpenSSL's MD5 routines for the a1hash password type */
+#include <openssl/md5.h>
+#endif
+
+#define SQLITE_LU  1024   /* maximum length of username - should correspond to 
field length */
+#define SQLITE_LR   256   /* maximum length of realm - should correspond to 
field length */
+#define SQLITE_LP   256   /* maximum length of password - should correspond to 
field length */
+
+enum sqlite3_pws_crypt {
+    MPC_PLAIN,
+#ifdef HAVE_CRYPT
+    MPC_CRYPT,
+#endif
+#ifdef HAVE_SSL
+    MPC_A1HASH,
+#endif
+};
+
+#ifdef HAVE_CRYPT
+static char salter[] = 
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
+#endif
+
 typedef struct moddata_st {
     sqlite3 *db;
     int txn;
@@ -41,8 +76,27 @@ typedef struct moddata_st {
     sqlite3_stmt *set_password_stmt;
     sqlite3_stmt *create_user_stmt;
     sqlite3_stmt *delete_user_stmt;
+    enum sqlite3_pws_crypt password_type;
 } *moddata_t;
 
+#ifdef HAVE_SSL
+static void calc_a1hash(const char *username, const char *realm, const char 
*password, char *a1hash)
+{
+#define A1PPASS_LEN SQLITE_LU + 1 + SQLITE_LR + 1 + SQLITE_LP + 1 /* 
user:realm:password\0 */
+    char buf[A1PPASS_LEN];
+    unsigned char md5digest[MD5_DIGEST_LENGTH];
+    int i;
+
+    snprintf(buf, A1PPASS_LEN, "%.*s:%.*s:%.*s", SQLITE_LU, username, 
SQLITE_LR, realm, SQLITE_LP, password);
+
+    MD5((unsigned char*)buf, strlen(buf), md5digest);
+
+    for(i=0; i<16; i++) {
+        sprintf(a1hash+i*2, "%02hhx", md5digest[i]);
+    }
+}
+#endif
+
 static sqlite3_stmt*
 _get_stmt(authreg_t ar, sqlite3 *db, sqlite3_stmt **stmt, const char *sql)
 {
@@ -132,28 +186,58 @@ _ar_sqlite_check_password(authreg_t ar, const char 
*username, const char *realm,
                          char password[257])
 {
 
-    sqlite3_stmt *stmt;
+    char db_pw_value[257];
+#ifdef HAVE_CRYPT
+    char *crypted_pw;
+#endif
+#ifdef HAVE_SSL
+    char a1hash_pw[33];
+#endif
     moddata_t data = (moddata_t) ar->private;
-    int res, ret=1;
-    char *sql =
-       "SELECT username FROM authreg WHERE username = ? AND password = ? AND 
realm = ?";
+    int ret=1;
 
     log_debug(ZONE, "sqlite (authreg): check password");
 
-    stmt = _get_stmt(ar, data->db, &data->check_password_stmt, sql);
-    if (stmt == NULL) {
-       return 1;
+    ret = _ar_sqlite_get_password (ar, username, realm, db_pw_value);
+    if (ret)
+        return ret;
+
+    switch (data->password_type) {
+        case MPC_PLAIN:
+                ret = (strcmp (password, db_pw_value) != 0);
+                break;
+
+#ifdef HAVE_CRYPT
+        case MPC_CRYPT:
+                crypted_pw = crypt(password,db_pw_value);
+                ret = (strcmp(crypted_pw, db_pw_value) != 0);
+                break;
+#endif
+
+#ifdef HAVE_SSL
+        case MPC_A1HASH:
+                if (strchr(username, ':')) {
+                    ret = 1;
+                    log_write(ar->c2s->log, LOG_ERR, "Username cannot contain 
: with a1hash encryption type.");
+                    break;
+                }
+                if (strchr(realm, ':')) {
+                    ret = 1;
+                    log_write(ar->c2s->log, LOG_ERR, "Realm cannot contain : 
with a1hash encryption type.");
+                    break;
+                }
+                calc_a1hash(username, realm, password, a1hash_pw);
+                ret = (strncmp(a1hash_pw, db_pw_value, 32) != 0);
+                break;
+#endif
+
+        default:
+        /* should never happen */
+                ret = 1;
+                log_write(ar->c2s->log, LOG_ERR, "Unknown encryption type 
which passed through config check.");
+                break;
     }
 
-    sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
-    sqlite3_bind_text(stmt, 2, password, -1, SQLITE_STATIC);
-    sqlite3_bind_text(stmt, 3, realm, -1, SQLITE_STATIC);
-    
-    res = sqlite3_step(stmt);
-    if (res == SQLITE_ROW) {
-       ret = 0;
-    }
-    sqlite3_reset(stmt);
     return ret;
 }
 
@@ -173,7 +257,25 @@ _ar_sqlite_set_password(authreg_t ar, const char 
*username, const char *realm,
        "UPDATE authreg SET password = ? WHERE username = ? AND realm = ?";
     
     log_debug(ZONE, "sqlite (authreg): set password");
-    
+
+#ifdef HAVE_CRYPT
+    if (data->password_type == MPC_CRYPT) {
+       char salt[39] = "$6$rounds=50000$";
+       int i;
+
+       srand(time(0));
+       for(i=0; i<22; i++)
+           salt[16+i] = salter[rand()%64];
+
+       strcpy(password, crypt(password, salt));
+    }
+#endif
+#ifdef HAVE_SSL
+    else if (data->password_type == MPC_A1HASH) {
+       calc_a1hash(username, realm, password, password);
+    }
+#endif
+
     stmt = _get_stmt(ar, data->db, &data->set_password_stmt, sql);
     if (stmt == NULL) {
        return 1;
@@ -328,6 +430,22 @@ ar_init(authreg_t ar)
        sqlite3_busy_timeout(db, atoi(busy_timeout));
     }
 
+
+    /* get encryption type used in DB */
+    if (config_get_one(ar->c2s->config, 
"authreg.sqlite.password_type.plaintext", 0)) {
+        data->password_type = MPC_PLAIN;
+#ifdef HAVE_CRYPT
+    } else if (config_get_one(ar->c2s->config, 
"authreg.sqlite.password_type.crypt", 0)) {
+        data->password_type = MPC_CRYPT;
+#endif
+#ifdef HAVE_SSL
+    } else if (config_get_one(ar->c2s->config, 
"authreg.sqlite.password_type.a1hash", 0)) {
+        data->password_type = MPC_A1HASH;
+#endif
+    } else {
+        data->password_type = MPC_PLAIN;
+    }
+
     ar->private = data;
 
     ar->user_exists = _ar_sqlite_user_exists;


Reply via email to