Недавно возникла необходимость запретить кому угодно вход на FTP-сервер, то есть заблокировать анонимные входы.
Но вести отдельную базу данных с пользователями FTP неудобно и тем более неэффективно.
Я подумал-подумал и решил авторизовать пользователей с помощью учетной записи на форуме, в моем случае SMF. Пользователи хранятся в таблице MySQL smf_members, имя пользователя — вmemberName, а пароль — в passwd. ProFTPD имеет хорошо документированную возможность получать учетные данные из PostgreSQL/MySQL. Но проблема в том, что форум хранит пароли в SHA1-хеше, а в начало пароля добавляется имя пользователя в нижнем регистре.
А ProFTPD ожидает, что из SQL-запроса к базе данных ему будет возвращен пароль в форматах MySQL PASSWORD(), Crypt или Plaintext. Разумеется, никакого SHA1 там нет, не говоря уже о том, что к паролю добавляется еще и имя пользователя.
Гуглил день, потом другой, все на это жалуются, но готового решения никто не предлагает. Что ж, наше дело не такое уж и сложное.
Итак, что мы имеем? Существует параметр SQLAuthTypes, указывающий метод шифрования пароля.
Нужного мне варианта нет, поэтому открываем исходники.
Последняя сборка RC была загружена с домашнего сайта ProFTPD, на этот раз 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 *));
Интересный.
Немного побегав по исходному коду, найдя достаточно примеров вызова этой функции (что странно, ничего из этого действительно недокументировано, а в Интернете 0 результатов по запросу «proftpd sql_register_authtype»), стало понятно, что это все сводится к созданию callback-функции, которая принимает в качестве параметров пароль в чистом виде и хеш, полученный из mysql, проверяет, соответствует ли пароль хешу, и возвращает результат проверки с возвратом.
Ну, это звучит легко.
Я создал собственный модуль 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), "x", 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. Я скомпилировал его, не забыв добавить опцию для настройки --with-shared=mod_sql_auth_smf, изменил параметр в proftpd.conf SQLAuthTypes на SMF и добавил строку LoadModule mod_sql_mysql.c. Собрал, проверил, работает! Но только в том случае, если пользователь User1 с паролем PassWd22 указывает в качестве пароля user1PassWd22. Ну, это проще.
Найдя функцию обратного вызова в 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 авторизовать пользователей, используя учетные данные любого форума, блога и скоро.
Теги: #linux #Системное администрирование #C++ #MySQL #proftpd #smf
-
Icann Приостановила Аккредитацию Freenom
19 Oct, 24 -
Джон Кармак О Статическом Анализе Кода
19 Oct, 24 -
«Если Ветер Сорвет Крышу…»
19 Oct, 24