Несмотря на то, что сейчас на дворе XX век, кое-кто по-прежнему продолжает использовать архаизм под названием "факсы". У нашей фирмы есть несколько контрагентов, которых хлебом не корми, дай что-нибудь прислать по факсу. Ну никак не освоят сакральных знаний по владению электронной почтой дородные тётеньки из бухгалтерии какого-нибудь ООО "ГлавЗаготСнабСбытЗерно".
До сих пор у нас был в той или иной степени криво работающий факс-сервер на виндовой машине, от которого мне в один прекрасный момент нестерпимо захотелось избавиться. Соответственно, решил поднять факс-ресивер на линуксовой файлопомойке.
[ Читать дальше]Из закромов of Rodina достал вот такой девайс и подключил его через переходник "USB↔RS232" к серваку.
Тут сразу возникла первая микропроблема. USB-переходник по умолчанию отображается в системе как "/dev/ttyUSB0". Но, во-первых, цифра в конце может быть любой. Во-вторых, для лучшей совместимости хотелось бы создать на неё символическую ссылку типа "/dev/modem". Что делать?
На помощь приходит udev. Заклинаем его волшебством типа
udevadm info --attribute-walk -n /dev/ttyUSB0 | sed -n '/FTDI/,/serial/p'
И получаем от него ответ вроде
ATTRS{manufacturer}=="FTDI"
ATTRS{product}=="USB HS SERIAL CONVERTER"
ATTRS{serial}=="FTDZZBUH"
Примечание: у меня FTDI-шный конвертер. В вашем случае он может быть и другим.
Видим, что у этой штуки есть такой атрибут как "серийный номер" (serial), выделил жирным шрифтом. Отлично, к нему и "привяжемся". Создадим новое правило в "/etc/udev/rules.d":
echo 'SUBSYSTEM=="tty", ATTRS{serial}=="FTDZZBUH", GROUP="dialout", MODE="0660", SYMLINK+="modem"' >> /etc/udev/rules.d/80-usrmodem.rules
Потом попросим udev перечитать конфигурацию
udevadm control --reload-rules
Затем ножками прогуляемся до сервера. Нужно выдернуть из USB-порта конвертер и подключить обратно. Вуаля, udev создал символическую ссылку "/dev/modem", которая ведет на "/dev/ttyUSB0". Ура.
Теперь надо выбрать софтинку, которая будет собственно слушать наш tty и принимать факсы, если оный появится на горизонте. Под Linux-ом есть две распространённые программы: mgetty и hylafax. С последним монстром мне связываться совсем не хотелось, поэтому я выбрал mgetty.
Работает она очень просто. Запускается, проверяет свободен ли модем. Если да, то подключается к нему, и тупо ждёт от него команды "RING". Как только получает такую команду, начинает действовать согласно инструкции конфигов. Если это факс, то принимает его, кладет в формате "G3 Fax" в папочку "/var/spool/fax/incoming", сбрасывает модем и завершает свою работу. Соответственно, как и всякий tty, запускать её нужно непосредственно из "/etc/inttab":
echo 'T3:2345:respawn:/sbin/mgetty -s 57600 -F -C "cls2" -x 2 /dev/modem' >> /etc/inittab
где "respawn" — автоматически перезапускать после завершения, дальше скорость передачи информации с модемом, "-F" — принимать только факсы (не данные), "cls2" — класс факса (об этом чуть ниже), "2" — уровень детализации логов, последним параметром идёт указание устройства (модема).
Произносим заклинание "init q" дабы init перечитал конфигурацию, убеждаемся что mgetty запустился, пробуем что-нибудь послать и проверяем что всё дошло и файлик с факсом "упал" в нужную папочку.
А вот дальше в процессе тестирования обнаружились форменные засады. Факсы в стандартном разрешении проходили более-менее нормально, а вот в чётком (fine) разрешении обламывались. Стал гуглить. Оказывается, у курьеров слишком кривая прошивка. Работать как class2-факсы они совершенно не приспособлены. А class1 не умеет сама mgetty. Вот подстава так подстава.
Небольшое лирическое отступление. "Класс" факс-модема характеризует количество операций, которые он способен проделывать без участия центрального процессора (CPU), то есть так называемый "offload". Не вдаваясь в подробности, в случае с class1 кадры для передачи в телефонную линию формирует CPU, а при class2 - сам модем.
Дык вот, mgetty не умеет class1, а Courier крайне фигово работает в роли class2. Что делать? Пришлось супротив своей воли поэкспериментировать с hylafax. В-принципе, хорошая софтина. Достаточно грамотно написана, удобна, легко конфигурируется. Даже некое подобие "wizard"-ов имеется (да, да, next, next, finish). Попробовал погонять её вместе с курьером в режиме class1. Один раз у меня даже принялся факс в fine-качестве. Но потом что-то перестали "ходить" даже стандартные. Короче, эксперимент провалился.
Тогда я порылся в zакромах of Rodina поглубже и извлёк из широких штанин вот такой модем:
В своё время легендарный был девайс, ага. Подключил его. И о чудо! Он "из коробки" нормально умеет class2 fax и прекрасно дружит с getty. Ряд экспериментов показал, что факсы принимаются вполне стабильно. Поэтому порешили запустить на production именно его.
Но это ещё не happy end. Мало факс просто принять. Надо его ещё либо послать сотруднику на электропочту, либо выложить в какую-нибудь расшаренную папочку. И да, ещё придётся его как-то конвертировать из формата G3 в какой-нибудь более понятный современным смотрелкам стандарт. Поскольку я заранее не знаю кому именно пришел факс (а CallerID наша АТСка в имеющейся конфигурации вовнутрь отдавать не умеет), то я выбрал второй способ. А преобразовывать буду в PNG. Как же это всё реализовать?
По завершении сеанса связи mgetty автоматом запускает скрипт по некому заранее вкомпилированному пути. Для версии из Debian-овских репозиториев этот скрипт называется "/etc/mgetty/new_fax". Соответственно, кладем в этот файлик скрипт, который делает то что мне нужно. В моём случае он весьма примитивен. Делал для себя, поэтому особо на кроссплатфроменность не заморачивался, абсолютные пути прописывал прям в коде. Знаю, что некрасиво. И да, поскольку скрипт запускается из mgetty, которая является потомком init-а, то никаких там локалей и PATH-ов по умолчанию не задано. Надо об этом помнить.
Ну а сам скриптик получился таким:
#!/bin/sh
PATH=/usr/sbin:/usr/local/bin:/usr/bin:/bin:$PATH
DIRPRE="/var/ftp/shared/Fax"
LOG=
export LANG=ru_RU.UTF-8
die ()
{
echo $1
[ -n "$LOG" ] && echo "$1" >> "$LOG"
exit 1
}
# Окей, получили факс
# Сперва нужно проверить есть ли папочка с нужной датой
[ -d $DIRPRE ] || die "Directory root tree does not exist"
YEAR=`/bin/date +%Y`
MONTH=`/bin/date +%m_%B`
DAY=`/bin/date +%d_%A`
TIME=`/bin/date +%H.%M`
[ -d "$DIRPRE/$YEAR" ] || /bin/mkdir "$DIRPRE/$YEAR"
[ -d "$DIRPRE/$YEAR/$MONTH" ] || /bin/mkdir "$DIRPRE/$YEAR/$MONTH"
[ -d "$DIRPRE/$YEAR/$MONTH/$DAY" ] || /bin/mkdir "$DIRPRE/$YEAR/$MONTH/$DAY"
[ -d "$DIRPRE/$YEAR/$MONTH/$DAY/$TIME" ] || /bin/mkdir "$DIRPRE/$YEAR/$MONTH/$DAY/$TIME"
# Пишем в лог аргументы
[ -n "$LOG" ] && echo "$@" >> "$LOG"
# Проверяем количество аргументов. Их должно быть как минимум четыре
[ "$#" -lt 4 ] && die "Too few arguments"
# Первые два аргумента выбрасываем, они нас не интересуют
shift ; shift
NUM=$1
[ "$NUM" -lt 1 ] 2>/dev/null && die "Too few pages, must be greater than zero"
[ "$?" -gt 1 ] && die "Illegal number of pages, must be integer"
shift
# Конвертируем и переносим каждую страницу в нужную папочку
NUM=1
for i in $@
do
NUMSTR=$(/usr/bin/printf "%02d" $NUM)
FILEPATH="$DIRPRE/$YEAR/$MONTH/$DAY/$TIME/$NUMSTR. png"
# Если вдруг такой файл уже существует... мало ли, стрелки перевели назад
while [ -e "$FILEPATH" ]
do
NUM=$(($NUM+10))
NUMSTR=$(/usr/bin/printf "%02d" $NUM)
FILEPATH="$DIRPRE/$YEAR/$MONTH/$DAY/$TIME/$NUMSTR. png"
done
RESOLUTION=`/usr/bin/basename $i | /bin/sed 's/.\(.\).*/\1/'`
if [ "$RESOLUTION" = "n" ]
then
STRETCH=-s
else
STRETCH=
fi
[ -r "$i" ] && /bin/cat $i | /usr/bin/g32pbm $STRETCH | /usr/bin/pnmtopng > "$FILEPATH"
NUM=$(($NUM+1))
/bin/rm "$i"
done
exit 0
Вот как-то так у меня сейчас и принимаются факсы.