Как стать автором
Обновить

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

Время на прочтение3 мин
Количество просмотров2.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 авторизировать пользователей по учетным данным любого форума, блога, и так далее.
Теги:
Хабы:
Всего голосов 31: ↑28 и ↓3+25
Комментарии30

Публикации

Истории

Работа

Ближайшие события