Recently there was a need to ban someone from entering the FTP server, that is, to block access to anonymous. But to maintain a separate database with FTP users is inconvenient, and moreover inefficient. I thought and thought, and decided to authorize users on the forum account, in my case SMF. Users are stored in the MySQL table smf_members, the username in memberName, and the password in passwd. ProFTPD has a well-documented ability to get credentials with PostgreSQL / MySQL. But the trouble is, the forum passwords are stored in the SHA1 hash, and the user name is added to the beginning of the password in lower case. And ProFTPD expects that a password in MySQL PASSWORD (), Crypt, or Plaintext formats will be returned to it from a SQL query to the database. Of course, there is no SHA1, let alone the fact that the username is added to the password. Google day, the other, everyone complains about this, and no one offers a ready-made solution.
Well, our business is not tricky. So what do we have? There is a SQLAuthTypes parameter that indicates which method encrypts the password. I do not have the option, then open source. The last RC build was downloaded from the ProFTPD home site, at this point proftpd-1.3.3rc3. A quick inspection of contrib / mod_sql.c and contrib / mod_sql.h showed that there is a mechanism for adding your own authorization methods to the function:
int sql_register_authtype(const char *name, modret_t *(*callback)(cmd_rec *, const char *, const char *));
Interesting. Having run a little on the source code, having found enough examples of calling this function (which is strange, nothing of this is undoubtedly undocumented, and on the Internet by the query “proftpd sql_register_authtype” 0 results), it became clear that everything comes down to creating a callback function that takes as parameters The password is pure and the hash obtained from mysql, checks if the password matches the hash, and returns the result of the check with return. Well, it sounds easy. Created the contrib / mod_sql_auth_smf.c artwork module as follows:
#include "conf.h"
#include "mod_sql.h"
#define MOD_SQL_AUTH_SMF_VERSION "mod_sql_smf/0.1"
#if defined(HAVE_OPENSSL) || defined(PR_USE_OPENSSL)
#include <openssl/evp.h>
static modret_t *auth_smf(cmd_rec *cmd, const char *c_clear, const char *c_hash) {
const EVP_MD *md;
EVP_MD_CTX md_ctxt;
unsigned char mdval[EVP_MAX_MD_SIZE];
char hash[EVP_MAX_MD_SIZE * 2];
int mdlen, i;
OpenSSL_add_all_digests();
md = EVP_get_digestbyname("sha1");
if (!md)
return ERROR_INT(cmd, PR_AUTH_BADPWD);
EVP_DigestInit(&md_ctxt, md);
EVP_DigestUpdate(&md_ctxt, c_clear, strlen(c_clear));
EVP_DigestFinal(&md_ctxt, mdval, &mdlen);
for (i=0; i<mdlen; i++) {
sprintf(hash+(i*2), "%02x", mdval[i]);
}
return strcmp(hash, c_hash) ? ERROR_INT(cmd, PR_AUTH_BADPWD) : HANDLED(cmd);
}
static int sql_auth_smf_init(void) {
(void) sql_register_authtype("SMF", auth_smf);
return 0;
}
#endif
module sql_auth_smf_module = {
/* Always NULL */
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"sql_auth_smf",
/* Module configuration directive table */
NULL,
/* Module command handler table */
NULL,
/* Module auth handler table */
NULL,
/* Module initialization */
sql_auth_smf_init,
/* Session initialization */
NULL,
/* Module Version */
MOD_SQL_AUTH_SMF_VERSION
};
In this module, I used OpenSSL to implement SHA1 hashing a clean password, checking against the received hash, and returning the test result to mod_sql.c. Compiled, not forgetting to add an option to configure --with-shared = mod_sql_auth_smf, changed the parameter in proftpd.conf SQLAuthTypes to SMF and added the line LoadModule mod_sql_mysql.c. Gathered, checked, works! But only if the user User1 with the password PassWd22, will specify the user1PassWd22 as the password. Well, it's easier. Having found the callback function in mod_sql.c, changed it from this view:
')
mr = sah->cb(cmd, plaintext, ciphertext);
On this:
if(strcmp(sah->name, "SMF")==0) {
int i;
char namepasswd[106];
strncpy(namepasswd, cmd->argv[1], 25);
for(i=0;namepasswd[i];i++) namepasswd[i]=tolower(namepasswd[i]);
strncat(namepasswd, cmd->argv[2], 80);
mr = sah->cb(cmd, namepasswd, ciphertext);
} else
mr = sah->cb(cmd, plaintext, ciphertext);
That's all, the goal is achieved. If someone is interested in the details of setting up ProFTPD for working with MySQL, compilation subtleties, I will answer in the comments. The purpose of this article is not to give a complete step-by-step guide from scratch, but to briefly show how you can, with a little C knowledge, force ProFTPD to authorize users with the credentials of any forum, blog, and so on.