Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Администрирование *NIX систем > Авторизация в ProFTPD по учетным записям форума.


Автор: Imple 21.12.2009, 14:29
Авторизация пользователей в ProFTPD по учетным записям форума

Недавно появилась необходимость запретить вход кому попало на 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 авторизировать пользователей по учетным данным любого форума, блога, и так далее.

Автор: SparF 27.12.2009, 20:39
Спасибо. решение и правда интересное.
Единственная просьба. Чтобы это решение не затерялось в интернете - отпишите его, пожалуйста, в официальную рассылку ProFTPD.
В случае, если его включат в релиз - это спасет множество пользователей от сборки (пересборки) вручную и необходимости отслеживать новостные рассылки на тему "а не появилась ли критичная дыра в proftpd - и не придется ли мне пересобирать его...".
Что, согласитесь, достаточно важно.

Автор: Imple 27.12.2009, 20:43
SparF, согласен, но нужно немного вылизать код. Будет свободная минутка, займусь, отправлю. Спасибо Вам за интерес к статье. smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)