Setup of authentication of FTP users using WordPress database
This article describes how Pure-FTPd can use a WordPress database with phpass password hashing. The work is part of our work on repeatability of research papers in computer science.
It is motivated by WordPress users that like archival of their files to a FTP storage. Normally, this would be as easy as pointing Pure-FTPd to the database with user and passwords and tell it which table and attributes that it had to access. Unfortunately, which you properly already guessed since I had to write a blog-post about it. It is not as simple. WordPress uses a framework for hashing its passwords, and the approach is not compatible with the way Pure-FTPd supports hashing techniques.
The framework, PHP password hashing framework (phpass), is used to perform hashing. It have different fallback modes, and thus, if we wanted to implement our own hashing technique, we would have to support the same fall-back techniques in Pure-FTPd, as this would be somewhat wasted time, we instead implement an authentication method for Pure-FTPd, which uses phpass as the hashing back-end.
Pure-FTPd supports a custom authentication model, of which it is possible for an external application to communicate if a FTP user may access the FTP server. The model exposes several parameters as environmental parameters on user login. Username, password, local ip address, etc. When a user login, an external application is executed and can then use the variable values to perform authentication. As this model relies on us accessing the WordPress database to fetch the password hash, we also thought about another approach. i.e. extend Pure-FTPd with phpass hashing.
The latter solution offered a simple approach to authenticate a hash from the database (as we may use the existing database plugin for Pure-FTPd). However, the cost is having to update the source-code for each new release. Heck, I know this is bad practice, but what is the fun in life, if we can’t break the rules once in a while…
So we went along with the approach. First, we had to get phpass up and running as a simple application/script. This was archived by researching how WordPress stored and authenticated its users, as we wanted to mimic this approach, it was here we could find how they implemented it. We then created a simple script, that may be called with two arguments (the cleartext password and hashed password from the database) and return either 1 or 2 depending if the authentication was successful.
The source-code is as follows (which you may put in /usr/bin/phpass-wrapper.php and chmod +x it)
The wrapper alone can stand alone and work for both previous mentioned solution.
The next step is to connect Pure-FTPd and the phpass wrapper. In our latter approach, this is accomplished by adding the phpass authentication method.
diff -rup pure-ftpd-1.0.36/src/crypto.c pure-ftpd-1.0.36-phpass/src/crypto.c--- pure-ftpd-1.0.36/src/crypto.c 2011-04-17 08:05:54.000000000 -0700+++ pure-ftpd-1.0.36-phpass/src/crypto.c 2012-04-20 02:07:08.288870553 -0700@@ -47,6 +47,78 @@ static char *hexify(char * const result, return result; } +/**+ * characters used for Base64 encoding+ */+const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";++/**+ * encode three bytes using base64 (RFC 3548)+ *+ * @param triple three bytes that should be encoded+ * @param result buffer of four characters where the result is stored+ */+void _base64_encode_triple(unsigned char triple[3], char result[4])+{+ int tripleValue, i;++ tripleValue = triple[0];+ tripleValue *= 256;+ tripleValue += triple[1];+ tripleValue *= 256;+ tripleValue += triple[2];++ for (i=0; i<4; i++)+ {+ result[3-i] = BASE64_CHARS[tripleValue%64];+ tripleValue /= 64;+ }+}++/**+ * encode an array of bytes using Base64 (RFC 3548)+ *+ * @param source the source buffer+ * @param sourcelen the length of the source buffer+ * @param target the target buffer+ * @param targetlen the length of the target buffer+ * @return 1 on success, 0 otherwise+ */+int base64_encode(unsigned char *source, size_t sourcelen, char *target, size_t targetlen)+{+ /* check if the result will fit in the target buffer */+ if ((sourcelen+2)/3*4 > targetlen-1)+ return 0;++ /* encode all full triples */+ while (sourcelen >= 3)+ {+ _base64_encode_triple(source, target);+ sourcelen -= 3;+ source += 3;+ target += 4;+ }++ /* encode the last one or two characters */+ if (sourcelen > 0)+ {+ unsigned char temp[3];+ memset(temp, 0, sizeof(temp));+ memcpy(temp, source, sourcelen);+ _base64_encode_triple(temp, target);+ target[3] = '=';+ if (sourcelen == 1)+ target[2] = '=';++ target += 4;+ }++ /* terminate the string */+ target[0] = 0;++ return 1;+}+ /* Encode a buffer to Base64 */ static char *base64ify(char * const result, const unsigned char *digest,@@ -167,7 +239,6 @@ char *crypto_hash_sha1(const char *strin return hexify(result, digest, sizeof result, sizeof digest); } - /* Compute a simple hex MD5 digest of a C-string */ char *crypto_hash_md5(const char *string, const int hex)Only in pure-ftpd-1.0.36-phpass/src: .depsdiff -rup pure-ftpd-1.0.36/src/log_mysql.c pure-ftpd-1.0.36-phpass/src/log_mysql.c--- pure-ftpd-1.0.36/src/log_mysql.c 2012-03-15 18:01:37.000000000 -0700+++ pure-ftpd-1.0.36-phpass/src/log_mysql.c 2012-04-20 02:25:53.020895435 -0700@@ -324,7 +324,7 @@ void pw_mysql_check(AuthResult * const r char *escaped_decimal_ip = NULL; int committed = 1; int crypto_crypt = 0, crypto_mysql = 0, crypto_md5 = 0, crypto_sha1 = 0,- crypto_plain = 0;+ crypto_plain = 0, crypto_phpass = 0; unsigned long decimal_ip_num = 0UL; char decimal_ip[42]; char hbuf[NI_MAXHOST];@@ -419,6 +419,7 @@ void pw_mysql_check(AuthResult * const r crypto_mysql++; crypto_md5++; crypto_sha1++;+ crypto_phpass++; } else if (strcasecmp(crypto, PASSWD_SQL_CRYPT) == 0) { crypto_crypt++; } else if (strcasecmp(crypto, PASSWD_SQL_MYSQL) == 0) {@@ -427,6 +428,8 @@ void pw_mysql_check(AuthResult * const r crypto_md5++; } else if (strcasecmp(crypto, PASSWD_SQL_SHA1) == 0) { crypto_sha1++;+ } else if (strcasecmp(crypto, PASSWD_SQL_PHPASS) == 0) {+ crypto_phpass++; } else { /* default to plaintext */ crypto_plain++; }@@ -484,6 +487,25 @@ void pw_mysql_check(AuthResult * const r goto auth_ok; } }+ if (crypto_phpass != 0) {+ char str_clear_base64[512];+ char str_hashe_base64[512];++ base64_encode(password, strlen(password), str_clear_base64, 512 );+ base64_encode(spwd, strlen(spwd), str_hashe_base64, 512);++ char cmd[512];+ sprintf(cmd, "phpass-wrapper.php "%s" "%s"", str_clear_base64, str_hashe_base64);++ int r = system (cmd);++ if (r == 256)+ goto bye;+ else if (r == 512)+ goto auth_ok;+ else+ goto bye;+ } if (crypto_plain != 0) { if (*password != 0 && /* refuse null cleartext passwords */ strcmp(password, spwd) == 0) {diff -rup pure-ftpd-1.0.36/src/log_mysql.h pure-ftpd-1.0.36-phpass/src/log_mysql.h--- pure-ftpd-1.0.36/src/log_mysql.h 2011-05-01 18:22:54.000000000 -0700+++ pure-ftpd-1.0.36-phpass/src/log_mysql.h 2012-04-20 01:59:00.248859759 -0700@@ -6,6 +6,7 @@ #define PASSWD_SQL_MYSQL "password" #define PASSWD_SQL_MD5 "md5" #define PASSWD_SQL_SHA1 "sha1"+#define PASSWD_SQL_PHPASS "phpass" #define PASSWD_SQL_ANY "any" #define MYSQL_DEFAULT_SERVER "localhost" #define MYSQL_DEFAULT_PORT 3306The source expects the phpass-wrapper.php file to be accessible from the $PATH variable on execution. Also note that communication between the two processed is handled by encoding the password and username using base64 encoding. This is done to omit escaping special characters from the variables and thus make it safe to pass the parameters. The variables is decoded in the wrapper. The Pure-FTPd approach to put it in environment variables is also a possibility, but this is better, as it doesn’t require any global state before the wrapper is executed.
Now we have the baseline, the next step is to compile the Pure-FTPd project and install the new binary. If you’re on Ubuntu, you might want to execute configure with the following configuration parameters: ./configure –with-mysql –with-rfc2640 –with-cookie –with-altlog. Install the binary by either creating a new debian package or simply copying over an existing applied binary (hackisk).
At last, we are ready for the final setup. In /etc/pure-ftpd/db/mysql.conf (or your favorite place for your Pure-FTPd configuration), we add the following:
MYSQLCrypt phpass
MYSQLGetPW SELECT user_pass FROM wp_users WHERE user_login=’\L’
MYSQLDefaultUID 1000 // Favorite user id or update the uid query.
MYSQLDefaultGID 1000 // Fovorite group id or update the group query.
Then restart Pure-FTPs and enjoy your newly configured FTP server with support for phpass hashed passwords.
Pandalizious
Profiles