Proftpd с хранением пользователей в MySQL (FreeBSD)


Озадачился найти замену штатному ftp-серверу FreeBSD - не устраивали системные учётки. Пошастал по инету, посмотрел что есть в портах - и остановился на proftpd. Всё в общем-то хорошо с ним тока один косяк - документация в некоторых разделах неполная... Приходилось домысливать самостоятельно - благо на его сайте была пара примеров конфигов. Ставил из портов:
/usr/home/lissyara/>cd /usr/ports/
/usr/ports/>make search name='proftpd'
Port: proftpd-1.3.0.r3
Path: /usr/ports/ftp/proftpd
Info: Highly configurable ftp daemon
Maint: mharo@FreeBSD.org
B-deps: expat-2.0.0 gettext-0.14.5_1 gmake-3.80_2 libiconv-1.9.2_1
R-deps: rc_subr-1.31_1
WWW: http://www.proftpd.org/

Port: proftpd-mysql-1.3.0.r3
Path: /usr/ports/ftp/proftpd-mysql
Info: Highly configurable ftp daemon with MySQL support
Maint: thomas@goirand.fr
B-deps: expat-2.0.0 gettext-0.14.5_1 gmake-3.80_2
ldconfig_compat-1.0_5 libiconv-1.9.2_1 mysql-client-4.1.18_1
rc_subr-1.31_1 readline-5.1
R-deps: ldconfig_compat-1.0_5 mysql-client-4.1.18_1
rc_subr-1.31_1 readline-5.1
WWW: http://www.proftpd.org/

/usr/ports/>/usr/ports/>cd /usr/ports/ftp/proftpd-mysql
/usr/ports/ftp/proftpd-mysql/>make && make install && make clean
Затем лезет синее окошко, с опциями. БД MySQL у меня уже стояла, поэтому я её и выбрал - с целью запихать туда пользователей. Ну и ещё некоторые - с целью в дальнейшем разобраться, что есть что:

Options for proftpd-mysql 1.3.0.r3

[ ] IPV6 Use IPv6
[ ] LDAP Use LDAP
[X] MYSQL Use MySQL
[ ] POSTGRESQL Use Postgres
[ ] OPENSSL Include mod_tls
[X] QUOTA Include mod_quota
[ ] IFSESSION Include mod_ifsession
[X] README Include mod_readme
[X] RATIO Include mod_ratio
[X] REWRITE Include mod_rewrite
[X] WRAP Include mod_wrap
[ ] RADIUS Include mod_radius
После инсталляции топаем править конфиг /usr/local/etc/proftpd.conf:

# Конфиг proftpd - 2006-02-19


# имя сервера - показывается коннектящимся клиентам
ServerName "Main FTP servant :)"
# тип сервера (даже не тип самого сервера, а тип его запуска,
# standalone/inetd - сам или через inetd)
ServerType standalone
# смысл следующей директивы такой: если клиент коннектится не на имя
# а на IP или на виртуальный хост, не описанный в конфиге, то при
# установке в `off` он получит отлуп, если же установлено `on` то
# он будет обслужен `сервером по-умолчанию`
DefaultServer on
# e-mail администратора (по идее для каждого ВиртуалХоста его можно
# поставить разный - но я делаю один сервер без извращений с
# виртуальными хостами)
ServerAdmin admin@lissyara.su
# файло где хранится инфа о сессиях
#ScoreboardFile /var/run/proftpd.scoreboard

# порт на котором работает сервер
Port 21

# Маска с которой создаются новые файлы (не совсем маска - маска получается
# из этого значения, путём его вычитания из 777 - т.е. в даном случае получится
# маска 755)
Umask 022

# Максимальное число `детей` (работает только в standalohe режиме)
# необходимо для защиты от атак типа `отказ в обслуживании` да и
# от перегрузки сервера поможет :)
MaxInstances 30

# Юзер от которого работает сервер
User ftp
# группа, под которой работает сервер
Group ftp


# Тип авторизации (на самом деле - в каком виде хрянятся
# пароли в БД - в данном случае - открытым текстом)
SQLAuthTypes Plaintext
# Кого и как аутентифицируем - on - всех и вся :)
# Но - если поставить `on` то он ломится в БД за группами.
# мне группы никчему. Посему поставил `users`
SQLAuthenticate users
# инфа для соединения с MySQL сервером:
# имя_базы_данных@хост_где_MySQL:порт имя_пользователя пароль
SQLConnectInfo ftp@localhost:3306 ftp ftp
# в каком порядке вернёт поля запрос - первое поле, это
# имя таблицы, где лежат пользователи
SQLUserInfo `users_table` `username` `password` `uid` `gid`
`homedir` `shell`
# должен ли быть у юзера (для того, чтобы он мог коннектится),
# `реальный` shell описанный в /etc/shells
RequireValidShell off
# лог файл работы с SQL
SQLLogFile /var/log/proftpd.log

# Вот тут моя натура склонная к ведению логов на всё в
# БД MySQL смогла разыграться на полную катушку :)
# Записываем удачные логины в БД. Общий смысл такой - создаём
# именованую кверю, с указанием что мы должны сохранять
SQLLog PASS counter_login
SQLNamedQuery counter_login UPDATE "`last_login`=UNIX_TIMESTAMP(),
`login_count`=`login_count`+1 WHERE
`username`='%u'" `users_table`
# пишем неудачные логины в БД
SQLLog ERR_PASS counter_err
SQLNamedQuery counter_err UPDATE "`last_err_login`=UNIX_TIMESTAMP(),
`err_login_count`=`err_login_count`+1 WHERE
`username`='%U'" `users_table`

# логируем что сохраняет и тащщит с сервера:
# переменные
# %u - имя пользователя (с которым залогинился)
# %f - полный путь и имя файла который был скачан
# %b - число байт, которые были скачаны
# %h - имя клиента (из DNS), если не удалось разрешить - IP
# %a - IP-адрес клиента
# %m - имя команды полученной от клиента (RETR/STOR)
# %T - время (секунд) ушедшее на передачу файла клиенту

SQLLog RETR,STOR log_story_transfer
SQLNamedQuery log_story_transfer INSERT "'',
UNIX_TIMESTAMP(),'%u',
'%f', '%b', '%h',
'%a', '%m', '%T'"
`xfer_table`
# записываем ошибки при сохранении и чтении файлов
# (в одну строку не влезли - но работает и в таком виде :))
SQLLOG ERR_RETR,ERR_STOR,ERR_DELE,ERR_RMD,ERR_RNTO
log_err_modify
SQLNamedQuery log_err_modify INSERT "'',
UNIX_TIMESTAMP(),
'%u', '%f', '%h',
'%a', '%m'" `xfer_errors`

# если вылезет проблема, типа тормозов при подключении
# (в момент установления коннекта `задумывается` на 10-20 секунд)
# то раскомментируйте следующие две строки
#UseReverseDNS off
#IdentLookups off



# если надо чтобы ВСЕ пользователи по ftp были ограниченны
# своей домашней директорией, то надо раскомментировать
# следующую строку:
#DefaultRoot ~
# у меня хитрее сделано - себе я разрешил шариться по всему серверу
# а остальных за`chroot`ил. Если, например, надо чтобы пользователи
# могли по серверу шариться а анонимоусы нет, то надо указать !users
# также можно указать определённую группу.
DefaultRoot ~ !lissyara

# Директории
<Directory ~>
AllowOverwrite on
<Limit Write>
AllowAll
</Limit>
<Limit READ>
AllowAll
</Limit>
</Directory>


<Anonymous /usr/home/ftp>
# пользователь от которого анонимоусы шарятся
User ftp
# группа анонимоусов
Group ftp
# альясы ананонимоусов (можно будет входить как ftp, а
# не anonymous)
UserAlias anonymous ftp
# максимально число анонимоусов
MaxClients 10 "Sorry, max %m users - try again later"
<Limit WRITE>
DenyAll
</Limit>
</Anonymous>
После того, как написали конфиг, создаём таблицы в MySQL (если вам не нужны извращения типа моих - с ведением логов в БД - то можно обойтись тока таблицей `users_table`):

--
-- Table structure for table `users_table`
--

DROP TABLE IF EXISTS `users_table`;
CREATE TABLE `users_table` (
`unic_id` int(11) NOT NULL auto_increment,
`username` varchar(20) NOT NULL,
`password` varchar(20) NOT NULL,
`groupname` varchar(24) NOT NULL,
`uid` int(11) NOT NULL,
`gid` int(11) NOT NULL,
`homedir` varchar(50) NOT NULL,
`shell` varchar(20) NOT NULL,
`last_login` int(15) NOT NULL,
`login_count` int(15) NOT NULL,
`last_err_login` int(15) NOT NULL,
`err_login_count` int(15) NOT NULL,
PRIMARY KEY (`unic_id`)
) ENGINE=MyISAM COMMENT='Таблица пользователей';

--
-- Dumping data for table `users_table`
--

INSERT INTO `users_table` VALUES (1,'lissyara','123','lissyara',
1001,1001,'/usr/home/lissyara','/sbin/nologin',0,0,0,0);
INSERT INTO `users_table` VALUES(2, 'admin','123', 'ftp',
'1000', '1000', '/usr/users/admin', '/sbin/nologin',0,0,0,0)



--
-- Table structure for table `xfer_errors`
--

DROP TABLE IF EXISTS `xfer_errors`;
CREATE TABLE `xfer_errors` (
`unic_id` int(32) NOT NULL auto_increment,
`timestamp` int(15) NOT NULL,
`user_name` varchar(64) NOT NULL,
`file_and_path` tinytext NOT NULL,
`client_name` varchar(127) NOT NULL,
`client_IP` varchar(15) NOT NULL,
`client_command` varchar(5) NOT NULL,
PRIMARY KEY (`unic_id`)
) ENGINE=MyISAM COMMENT='Таблица ошибок при работе';

--
-- Dumping data for table `xfer_errors`
--


--
-- Table structure for table `xfer_table`
--

DROP TABLE IF EXISTS `xfer_table`;
CREATE TABLE `xfer_table` (
`unic_id` int(32) NOT NULL auto_increment,
`timestamp` int(15) NOT NULL,
`user_name` varchar(64) NOT NULL,
`file_and_path` tinytext NOT NULL,
`bytes` int(15) NOT NULL default '0',
`client_name` varchar(127) NOT NULL,
`client_IP` varchar(15) NOT NULL,
`client_command` varchar(5) NOT NULL,
`send_time` varchar(9) NOT NULL default '0',
PRIMARY KEY (`unic_id`)
) ENGINE=MyISAM COMMENT='Таблица, чё приняли-передали';

--
-- Dumping data for table `xfer_table`
--
Заводим в системе пользователя `ftp` командой `adduser`, после чего создаём файло для логов и даём на него права:
/usr/home/lissyara/>touch /var/log/proftpd.log
/usr/home/lissyara/>chown ftp:wheel /var/log/proftpd.log
Создаём директорию для пользоваетля `admin` и даём на неё права пользователю `ftp`:
/usr/home/lissyara/>su
/usr/home/lissyara/>mkdir -p /usr/users/admin
/usr/home/lissyara/>chown ftp:ftp /usr/users/admin
Вносим строку в /etc/rc.conf и запускаем:

/usr/local/etc/rc.d/>echo 'proftpd_enable="YES"' >> /etc/rc.conf
/usr/local/etc/rc.d/>./proftpd.sh start
Starting proftpd.
/usr/local/etc/rc.d/>
В общем-то всё. Если что-то не работает смотрим логи MySQL - правильные ли запросы идут к БД, идут ли они вообще, также смотрим /var/log/messages. В крайнем случае запускаем proftpd с параметрами типа -q -d 10...

P.S. надо сразу отметить, что если создаётся учётка для пользователя, который есть в системе (в данном примере - `lissyara`) и ему необходимо дать теже права, что у него есть - то имя пользователя и пароль должны совпадать с системными.

P.S.2 Кстати, если вместо Plaintext поставить Backend то можно в БД хранить пароли зашифрованные средствами самого MySQL (функция PASSWORD()).

P.S.3 Если пароли зашифрованы - то пользователь автоматом chroot`ится - ибо для проверки системного пароля он должен быть в БД незашифрованным.

Обновлено: 12.03.2015