Pull to refresh

Авторизация пользователей в ProFTPD по учетным записям форума

Reading time3 min
Views2.2K
Недавно появилась необходимость запретить вход кому попало на FTP сервер, то есть заблокировать вход под anonymous. Но содержать отдельную базу с FTP пользователями неудобно, а более того неэффективно. Думал-думал, и решил авторизировать пользователей по учетной записи форума, в моем случае SMF. Пользователи хранятся в MySQL таблице smf_members, имя пользователя в memberName, а пароль в passwd. У ProFTPD есть хорошо документированная возможность получать учетные данные с PostgreSQL / MySQL. Но вот беда, у форума пароли хранятся в хеше SHA1, да и в начало пароля добавляется имя пользователя в нижнем регистре. А ProFTPD ожидает, что ему из SQL-запроса к базе вернется пароль в форматах MySQL PASSWORD(), Crypt, либо Plaintext. Само собой, никакого SHA1 там нет, не говоря уже о том, что к паролю еще добавляется имя пользователя. Гуглил день, другой, все жалуются на подобное, а готового решения никто не предлагает.

Ну, наше дело не хитрое. Значит, что мы имеем? Есть параметр SQLAuthTypes, который указывает, каким методом криптован пароль. Нужного мне варианта нет, значит открываем исходники. С домашнего сайта ProFTPD был скачен последний RC-билд, на этот момент proftpd-1.3.3rc3. Беглый осмотр contrib/mod_sql.c и contrib/mod_sql.h, показал, что есть механизм добавления своих методов авторизации функцией:

int sql_register_authtype(const char *name, modret_t *(*callback)(cmd_rec *, const char *, const char *));

Интересно. Немного побегав по исходникам, найдя достаточно примеров вызова этой функции (что странно, ничего этого толком недокументированно, да и в интернетах по запросу «proftpd sql_register_authtype» 0 результатов), стало ясно, что все сводится к созданию callback функции, которая принимает в качестве параметров пароль в чистом виде и хеш полученный из mysql, проверяет соответствует ли пароль хешу, и return’ом возвращает результат проверки. Ну, вроде бы звучит несложно. Создал кустарный модуль contrib/mod_sql_auth_smf.c следующего содержания:

#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

};

В этом модуле я средствами OpenSSL реализовал хеширование SHA1 чистого пароля, сверку с полученным хешем, и возвратом результата проверки в mod_sql.c. Скомпилировал, не забыв добавить опцию к configure --with-shared=mod_sql_auth_smf, изменил параметр в proftpd.conf SQLAuthTypes на SMF и добавил строчку LoadModule mod_sql_mysql.c. Собралось, проверил, работает! Но только в том случае, если пользователь User1 с паролем PassWd22, укажет в качестве пароля user1PassWd22. Ну, это уже проще. Найдя вызов callback функции в mod_sql.c, изменил ее с такого вида:

mr = sah->cb(cmd, plaintext, ciphertext);

На такой:

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);

Вот и все, цель достигнута. Если кого-то интересуют подробности настройки ProFTPD для работы с MySQL, тонкости компиляции, отвечу в комментариях. Цель данной статьи – не дать полное пошаговое руководство с нуля, а кратко показать, как можно с небольшими знаниями C, заставить ProFTPD авторизировать пользователей по учетным данным любого форума, блога, и так далее.
Tags:
Hubs:
+25
Comments30

Articles