Официальная возможность получить лицензионный софт бесплатно.
Giveaway of the Day
Это не реклама!

Щелкните для получения прогноза по Биробиджану


четверг, 29 августа 2013 г.

Планирование автоматического включения/отключения переадресации в зимбре

Automating the turning on/off the mail forwarding in Zimbra

Система состоит из 6 исполняемых файлов-скриптов, 5 на shell-script, 1 на #awk, разбитых на две группы: ввод данных и обработка.

Ввод (и частичная обработка) выполняется по цепочке:

zimtest --> zimtest1
zimtest1 вызывает rrgtest и при необходимости zmonoff

Автоматическая обработка по расписанию выполняется zimparser, который вызывает zimawk.awk и zmonoff.

ZIMTEST

Сценарий-обёртка. При запуске без параметров выдает наикратчайшую справку. Запускается с параметрами:

zimtest дата_начала дата_окончания кого на_кого

Все параметры обязательны.

Обе даты задавать в русском формате дд.мм.гггг, год обязательно 4 цифрами. В качестве разделителя полей даты можно использовать точку, прямой слэш или их комбинацию - все разделители потом приводятся к слэшам.

В качестве кого можно указывать как полный емайл (напр. pupkin_vasya@наш_домен), так и только именную его часть (pupkin_vasya) - при проверке имени зимброй работают оба варианта.

На_кого - только полный емайл, поскольку эта строка просто будет вписана в соответствующее поле LDAP для кого. (однако, валидность этого адреса тоже проверяется и вписать несуществующего получателя не удастся)

Емайлы должны принадлежать нашему домену - автоматическая настройка автоматической пересылки на другие домены не разрешается.

ZIMTEST1

Основной скрипт ввода. Использует два скрипта - rregtest и zmonoff - для выполнения отдельных операций. rregtest используется как подключаемая библиотека, содержащая функцию проверки допустимости даты. Поскольку со времени начальной разработки эта функция сильно упростилась (я отдал все лишние проверки операционной системе), то возможно, что функция будет включена в состав zimtest1, а rregtest будет исключен из "комплекса".

zmonoff (ZiMbra ON/OFF) - скрипт, выполняющий, собственно, включение/выключение переадресации. Используется здесь в случаях, когда дата начала переадресации уже прошла или наступила сегодня для немедленного включения переадресации, либо когда дата отключения уже прошла или наступила сегодня, для немедленного отключения.

Переданные даты отдаются на проверку сценарию rregtest, а емайлы проверяются внутри скрипта силами зимбры. Изначально, опять же, я писал валидатор для емайлов, но потом отказался от него и запускаю zmprov ga. Чтобы не делать пустую проверку, для красоты получаю в ее ходе от зимбры человеческое имя пользователя. Пока оно просто выводится на экран, но в теории может пригодиться для чего-нибудь полезного.

Для тех же красивостей вычисляется количество дней между начальной и конечной датами действия переадресации. Сходный алгоритм используется чуть позже для вычисления времени, оставшегося до включения/отключения переадресации.

После проверки "а не указана ли конечная дата раньше начальной?" начинается собственно формирование файла данных для обработки по крону.

Если выясняется, что переданная дата уже наступила, то действие выполняется немедленно и в файл данных для дальнейшей обработки ничего не заносится. Проверка на случай, когда обе даты в прошлом, не делается и если так и есть, то сначала переадресация включается, а потом сразу выключается. Не очень оптимально, но уже лень было делать лишнюю проверку.

Если же переданная дата еще не наступила, то информация о ней дописывается в конец файла данных zmsched.dat в виде строки:

@секунды:действие:аккаунт_кого:емайл_на_кого:дата_по-русски

Например:

Переадресовывать почту Васи Пупкина на Петю Попкина с 25.09.2013
@1380027600:on:pupkin_vasya:popkin_petya@my.domain:25.09.2013

Отменить с 25.09.2013 переадресацию почты Васи Пупкина. Поле емайл_на_кого пусто, но оно есть - это строка нулевой длины между третьим и четвертым двоеточиями.
@1380027600:off:pupkin_vasya::25.09.2013

@секунды - дата выполнения действия в формате

date -d "дата 0:00" +@%s

то есть, количество секунд с начала "эпохи" до нуля часов нужного дня. Такой формат выбран для удобства сравнения в обработчике.

действие - принимает одно из трех возможных значений: on, off, skip. Действие skip будет описано ниже в описании обработчика zimawk.awk. Действия on и off самоочевидны: включить или выключить переадресацию для аккаунт_кого, указав, в случае включения емайл_на_кого.

аккаунт_кого - либо имя пользователя либо емайл пользователя, чью почту переадресовывать. Как говорилось выше, в этом поле достаточно использовать только имя аккаунта. Емайл тоже можно, но это целесообразно только при наличии на сервере нескольких доменов. Когда домен один, достаточно имени пользователя.

емайл_на_кого - емайл и только емайл, на который будет переадресовываться почта. Это просто строка, которая будет помещаться в соответствующее поле в LDAP-записи про аккаунт_кого. Если указано действие off, то содержимое этого поля может быть пустым - оно никак не анализируется в такой ситуации, хотя и обязано присутствовать.

дата_по-русски - то же самое, что @секунды, но в формате дд.мм.гггг. Поле добавлено только для удобочитаемости человеком. Теоретически возможно отказаться от первого поля @секунды, чтобы файл можно было править вообще вручную.

RREGTEST

Подключаемая библиотека-сценарий. Содержит только одну функцию validdate(), которая изначально задумывалась как полноценный валидатор дат, но позже была упрощена.

Дата проверяется на минимальную правильность по шаблону, примерно соответствующему дате в русском формате дд.мм.гггг. Причем этот шаблон очень нечеткий и допускает, например, 39.00.2016 (тридцать девятое число нулевого месяца). Это сделано умышленно, чтобы просто удостовериться, что в параметре передана именно дата, а не что-то другое: при любом большем несоответствии скрипт вываливается с exit 1, останавливая работу вызвавших его скриптов.

Дальше дата разбирается на запчасти и день меняется местами с месяцем, образуя дату в американском формате мм/дд/гггг: увы и ах, но юниксы были написаны американцами и штатная date никак не воспринимает на входе русский формат. Японский гггг/мм/дд, кстати, тоже.

Сформированную дату пытаемся вывести на экран, и тут уже пусть date сама проверяет ее допустимость. Неправильно что-то? Вываливаемся!

На случай, если дата допустимая, отдельно проверяется год - он должен быть не меньше предыдущего и не больше следующего. Сейчас просто выводится сообщение, если год неправильный, но это можно использовать для каких-нибудь более жестких проверок

ZIMPARSER

Скрипт-обёртка, запускаемый по крону. Устанавливает некоторые служебные переменные и вызывает собственно обработчик файла настроек.
Файл со скриптом обработчика и файл настроек должны находиться в одном с ним каталоге. Возможно, в будущем их местоположение будет передаваться в параметрах.

ZIMAWK.AWK

Основной обработчик файла данных zimsched.dat. Файл считывается построчно и если строка проходит ряд проверок, выполняется указанное в ней действие.
Проверки:
1. если в строке не пять полей, то строка считается неправильной и не обрабатывается, о чем делается запись в логе
2. если указано действие skip, то эта строка была обработана на прошлом проходе и ее обработка не требуется. Строка пропускается.

Прошедшая эти проверки строка считывается в массив для последующего переформирования файла данных. Если дата действия еще не наступила, то строка дальше не обрабатывается.

В подходящих для обработки строках действие заменяется с on или off на skip, чтобы удалить их на следующем проходе, и вызывается zmonoff для выполнения нужной операции.

После обработки всего файла создается временный файл zmsched.new, в который первой строкой выводится дата/время его создания, а далее построчно дописывается содержимое массива.

Старый файл данных переименовывается в zmsched.DOW, где DOW - номер дня недели, одна цифра, что позволяет вести историю изменений файла данных за последнюю неделю.

Файл zmsched.new копируется под именем zmsched.dat и система готова к новому циклу.

---
Использовались материалы:

Bash Scripting Guide

AWK

Еще awk и sed.

Под катом исходники.

ZIMTEST

#!/bin/bash
if [ "$1" = "" ]
    then
        echo $(basename $0) START_date FINISH_date FROM_account TO_account
        exit
fi
IAmHere=$(pwd)
# формируем файл с планировками включений-отключений
~/zimtest1 $1 $2 $3 $4 $5
cd $IAmHere


ZIMTEST1

#!/bin/bash
MYPATH=`dirname $0`
. $MYPATH/rregtest
SCHED_FILE=$MYPATH/zimsched.dat
echo checking email $3
su -c "zmprov ga $3>/dev/null" - zimbra
if [ $? != 0 ]; then echo Account NOT exists!; exit 1;
    else su -c "zmprov ga $3" - zimbra|grep displayName|sed -e "s/displayName/$3/"
fi
echo checking email $4
su -c "zmprov ga $4>/dev/null" - zimbra
if [ $? != 0 ]; then echo Account NOT exists!; exit 1
    else su -c "zmprov ga $4" - zimbra|grep displayName|sed -e "s/displayName/$4/"
fi
if [[ "$1" =~ ^[0-9][0-9]\.[0-9][0-9]\.20[1-9][0-9] ]]
    then echo $?:$1 seems to be OK
    else echo $?:$1 suspected
fi
validdate "$1" #все проверки правильности введенных дат, в т.ч. и на правильность года в функции validdate в файле rregtest
d1=$(date -d $reparsed_date +@%s) #reparsed_date формируется в файле rregtest
if [[ "$2" =~ ^[0-9][0-9]\.[0-9][0-9]\.20[1-9][0-9] ]]
    then echo $?:$2 seems to be OK
    else echo $?:$2 suspected
fi
validdate "$2"
d2=$(date -d $reparsed_date +@%s)
# чтобы не делать кучу таких преобразований по ходу работы
d1s=$(date -d $d1 +%d.%m.%Y)
d2s=$(date -d $d2 +%d.%m.%Y)

# Для сведения, а может и для дальнейшего использования: количество дней между этими датами
echo Date span: $d2s-$d1s=$(( ($(echo $d2|sed -e 's/@//')-$(echo $d1|sed -e 's/@//'))/3600/24 )) days
# А не перепутаны ли даты?
if [[ $d2 < $d1 ]]
    then
        echo ERROR: Finish date $d2s BEFORE start date $d1s
        exit 1
fi
dtoday=$(date -d "today 0:00" +@%s)
dtodays=$(date -d $dtoday +%d.%m.%Y)
# начинаем формировать команды для исполнения по крону
if [[ $d1 > $dtoday ]]
# сообщаем дату начала переадресации и сколько до нее осталось дней
    then
        echo Scheduling forward start on $d1s \(days before: $(( ($(echo $d1|sed -e 's/@//')-$(echo $dtoday|sed -e 's/@//'))/3600/24 ))\)
        echo $d1:on:$3:$4:$d1s>>$SCHED_FILE
# просроченное активируем сейчас, если оно еще актуально
    else
        if [[ $d2 > $dtoday ]] #время отмены переадресации еще не наступило?
            then
                echo Start date $d1s is due. Setting it up right now!
                $MYPATH/zmonoff on $3 $4
            else
                echo Forwarding is overdue: finish date $d2s is in the past. Do NOT set forwarding at all
        fi
fi
if [[ $d2 > $dtoday ]]
# сообщаем дату окончания переадресации и сколько до нее осталось дней
    then
        echo Scheduling forward finish on $d2s \(days before: $(( ($(echo $d2|sed -e 's/@//')-$(echo $dtoday|sed -e 's/@//'))/3600/24 ))\)
        echo $d2:off:$3::$d2s>>$SCHED_FILE
# просроченное и сегодняшнее деактивируем сейчас

    else
        echo Finish date $d2s is due. Resetting it right now!
        $MYPATH/zmonoff off $3
fi
echo Finished
exit


RREGTEST

#!/bin/bash
 

validdate(){

a=$1
if [ "$a" = "" ]; then
echo USAGE: `basename $0` date
echo date = d/m/yyyy
echo \".\" also allowed as date separator
    exit 1;
    a="3.08/2013";
fi 
# проверяем на общее соответствие шаблону
if [[ "$a" =~ [0-3]{0,1}[0-9]{1}(\.|\/)[0-1]{0,1}[0-9]{1}(\.|\/)20(11|12|13|14|15|16) ]]
    then echo Pattern matched $BASH_REMATCH;
    else echo Pattern NOT matched $?; exit 1;
fi
# Заменяем все точки на слеши, если есть
a=$(echo $a|sed -e 's/\./\//g')
# Разбиваем переданную дату на части и переставляем их местами, дописывая ведущие нули ко дню и месяцу, если надо
reparsed_date=$(awk -F / -v mydate=$a 'BEGIN {$0=mydate;
    if (length($2) == 1) {$2="0" $2};
    if (length($1) == 1) {$1="0" $1};
    print $2 "/" $1 "/" $3}')
# Год пригодится потом
dyo=$(awk -F / -v mydate=$a 'BEGIN {$0=mydate; print $3}') #'
echo reparsed_date $reparsed_date
date -d $reparsed_date +%d.%m.%Y>/dev/null
if [ $? -ne 0 ]; then echo "WRONG DATE PASSED";exit 1;fi
#год в диапазоне текущий-1..текущий+1?
if [ "$dyo" -lt "$[$(date +%Y)-1]" -o "$dyo" -gt "$[$(date +%Y)+1]" ]; then echo BAD YEAR; exit 1;else echo GOOD YEAR; fi
}


ZIMPARSER

#!/bin/bash
# Обрабатываем по крону файл данных, сформированный zimtest-ом
MYPATH=`dirname $0`
SCHED_FILE=$MYPATH/zimsched.dat
AWK_PROG=$MYPATH/zimawk.awk
LOGEXT=`date -d today +%u`
echo $MYPATH>/var/log/zimparser.log.$LOGEXT
awk -F \: -v MYP=$MYPATH -f $AWK_PROG $SCHED_FILE >>/var/log/zimparser.log.$LOGEXT

ZIMAWK.AWK

#!/usr/bin/awk

# формат полей во входном файле:
# дата действия в @-формате
# действие: on/off/skip (действие skip используется как метка для удаления строки при следующем проходе)
# кого переадресовать
# кому переадресовать (игнорируется для действия off. может быть пустым в этом случае)
# дата в формате дд.мм.гггг - просто для удобочитаемости человеком

# возможно стоит рассмотреть вариант, когда первое поле будет игнорироваться, а вместо него будет парситься только $5
# тогда будет возможна правка файла человеком без пересчета даты в @-формат

BEGIN {
print "AWK Started from " MYP
i = 0 #счетчик цикла на всякий случай
s[i,i]="" # массив прочитанных строк - будем из него формировать новый файл. Наверное.

# вот так хитро получаем дату (gawk.pdf p.75 4.9.6)
"date -d \"today 0:00\" +@%s" | getline today
sub(/\@/,//,today)

# получаем имя переданного файла без расширения - его потом будем подставлять при переименовании
("basename " ARGV[1] " " substr(ARGV[1],length(ARGV[1])-3))|getline myfile
# и его же - с расширением, чтбы не было некрасивых заморочек с путями при вызове не из текущего каталога при использовании ARGV
("basename " ARGV[1])|getline myfileExt

}

# красиво печатаем номер строки. Ага, для понтов.
{i++;printf "%0.3d:",i}

# пропускаем все строки, где нет 5 полей
NF != 5 {print "Неправильная строка: [" $0 "]"; next}

# пропускаем отработанные (== - сравнение, = - присвоение)
# не факт, что это понадобится, но пусть пока будет
$2 == "skip" {print $5 " уже отработано";next}

# обрабатываем правильные строки

{
# строку поэлементно запихиваем в массив - из него будем формировать новый файл
# для удобства отработанным ставим skip, чтобы удалить их при следующем проходе
# for (j=1;j<=5;j++) {s[i,j]=$j; printf "[%s]",s[i,j]}
# print
for (j=1;j<=5;j++) {s[i,j]=$j}

# отрезаем лидирующий @ из поля даты, чтобы не мешал сравнениям
sub(/\@/,//,$1)
}

# пропускаем те, чья дата еще не наступила
# если будем выводить массив в файл, то еще необработанные строки надо сохранить в массиве, а уж потом пропустить
$1 > today {print $5 " еще рано, пропускаем";next}

{
sub(/on/, "ВКЛЮЧИТЬ", $2)
sub(/off/, "ВыКЛЮЧИТЬ", $2)

print $2 " " $5 " " $3


# вызов скрипта включения/отключения
system(MYP "/zmonoff " s[i,2] " " s[i,3] " " s[i,4])

# помечаем строку как обработанную
s[i,2]="skip"
}

END {
mynewfile=MYP "/" myfile ".new"
"date"|getline JustNow
print "New results: [" JustNow "]" >mynewfile
for (k=1;k<=i;k++) {
    if (s[k,1] != "") {
        printf "%0.3d:",k
        for (j=1;j<=4;j++) {
#           printf "%s:",s[k,j]
            printf "%s:",s[k,j]>>mynewfile

            }
#       print s[k,5]
        print s[k,5]>>mynewfile
        }
    }
# для переименования получаем номер текущего дня недели
"date -d today +%u"|getline dayext

print "\nMoving " MYP "/" myfileExt " to " MYP "/" myfile "." dayext
mvcmd="mv " MYP "/" myfileExt " " MYP "/" myfile "." dayext
print mvcmd
system(mvcmd)

print "Copying new file " MYP "/" myfile ".new to " MYP "/" myfileExt
cpcmd="cp " mynewfile " " MYP "/" myfileExt
print cpcmd
system(cpcmd)

print "AWK Finished"}


ZMONOFF

#!/bin/bash
PrintResult(){
    if [ "$1" = 0 ]
        then echo OK;
        else echo ERROR: $1;
    fi
}
if [ "$1" = $"on" ]
    then
        echo Turning forwarding ON
            su -c "zmprov ma $2 zimbraFeatureMailForwardingEnabled TRUE" - zimbra
            PrintResult $?
        echo Setting forwarding address
            su -c "zmprov ma $2 zimbraPrefMailForwardingAddress $3" - zimbra
            PrintResult $?
    else
        echo Turning forwarding OFF
            su -c "zmprov ma $2 zimbraFeatureMailForwardingEnabled FALSE" - zimbra
            PrintResult $?
        echo Removing forwarding address
            su -c "zmprov ma $2 zimbraPrefMailForwardingAddress \"\"" - zimbra
            PrintResult $?
fi

Комментариев нет:

Отправить комментарий

Пожалуйста, воздержитесь от грубостей и персональных нападок.
Я не против матерщины, но она должна быть уместной и использоваться для выражения эмоций, а не в качестве основного средства выражения мыслей.