#!/bin/sh #Этот скрипт вызывается в момент, когда пользователь #успешно прошел авторизацию в биллинге. Задача скрипта - перестроить #файрволлы так, чтобы пользователь получил доступ в интернет # (C)2003-2008 Wapr Old. Ver 3.3 (04.2008) #0. Умолчания: До запуска биллинга должна быть создана цепочка BILL # с последним правилом -j DROP и в неё должны отправляться все пакеты # для которых необходимо управление доступом. (как минимум из FORWARD) # и запрос на прокси. # например так: # iptables -A FORWARD -i $WAN -o $LAN -j BILL # iptables -A FORWARD -i $LAN -o $WAN -j BILL # iptables -A INPUT -p tcp --dport 3128 -i $LAN -j BILL # где WAN/LAN - имена сетевых интерфейсов [ethX|pppX] #1. Скрипт проверяет наличие персональной юзерской цепочки по шаблону # BILL_${LOGIN} и если её нет, создает её. [BILL_Vasya] #2. В неё при создании добавляются в нужном порядке разрешающие # и запрещающие правила для всех необходимых адресов и критериев # ограничения скорости с возвратом пакетов через -j ACCEPT # Стоит учесть, что сюда попали пакеты ТОЛЬКО данного юзера, поэтому # IP юзера можно уже не учитывать #3. В цепочку BILL добавляется вызов юзерской цепочки для всех # входящих и исходящих пакетов данного юзера # iptables -I BILL -s $IP -j BILL_${LOGIN} # iptables -I BILL -d $IP -j BILL_${LOGIN} #4. Для отключения юзера надо удалить только 2 правила в цепочке BILL, # а его цепочка остается на будущее. (to do) #5. Допускается форвард до 15 портов по TCP и UDP отдельно. Список берется # либо из поля Userdata0, либо из файла в каталоге "/home/$LOGIN/portforward.txt" # формат записи: tcp=port1,port2... udp=port1,port2... # эквиваленные разделители: "="==":" ","==";" \n==\s==\t # Последняя строка файла не должна содержать правил и может быть пустой или # с комментарием, но она должна быть. # Пример создаваемого правила: # iptables -t nat -A PREROUTING_$LOGIN -i ppp+ -p tcp -m multiport \ # --dport 1234,5678,18273 -j DNAT --to-destination $IP # В случае попытки захвата занятого порта юзеру выдаётся предупреждение, а порт # исключается из запроса. #6. Если имя в биллинге не совпадает с именем на сервере, для поиска юзерского # файла допускается подмена из поля Userdata0: username=имя_на_сервере #7. Можно заблокировать порты назначения каждому юзеру записями в Userdata0 # rejecttcp=port1,port2... и rejectudp=port1,port2... # Блокирующие записи вносятся в персональную юзерскую цепочку и не мешают остальным # юзерам. Также нет ограничений на количество портов. # пример блокирующего правила: # iptables -A BILL_$LOGIN -p TCP --dport 25 -j REJECT #8. При анализе имени тарифа выделяются фрагменты "anlim-" и "abon-" для создания # особых правил для данных типов тарифов. # Также выделяется последние цифры от знака "-" до конца для получения тарифной скорости # в Kbit/sec по маске AnyTarifName-X, где X произвольное число. # Если числа нет или оно равно нулю - скорость не ограничена. # Т.е. название тарифа строится по формату: AnyTariffName[abon-][anlim-][-speed] # Login LOGIN=$1 #user IP IP=$2 #cash CASH=$3 #user ID ID=$4 DinM=( 0 31 28 31 30 31 30 31 31 30 31 30 31 ) D=`date '+%Y-%m-%d %H-%M-%S'` declare -i Month=$((10#${D:5:2})) declare -i Day=$((10#${D:8:2})) IPT="/usr/sbin/iptables" sgconf="/sbin/sgconf" bc="/usr/bin/bc" GW2="10.100.100.128" # роутер резерва GW2_ALLOW="/etc/stargazer/GW2_ALLOW" # скрипт включения юзера на резерве usersconf="/var/stargazer/users/$LOGIN/conf" usersstat="/var/stargazer/users/$LOGIN/stat" tariffs="/var/stargazer/tariffs" abon_key="abon-" # фрагменты имени тарифа, определяющие anlim_key="anlim-" # его класс declare -i TariffSpeed # тарифная скорость logfile="/var/log/stargazer-test" portforward="/home/$LOGIN/portforward.txt" # юзерский файл управления форвардом PortMin=1024 # допустимый диапазон портов для форварда PortMax=49152 CR=$'\n' LF=$'\r' TAB=$'\t' IFSold=$IFS declare -a arr # служебный массив if [ ! -e "$usersconf" ] ; then echo "ERROR: User file '$usersconf' not found" >> $logfile exit fi TariffName=`cat "$usersconf" | grep "Tariff=" | cut -d"=" -f2` # Парсинг поля Userdata0 # Получить массивы портов для форварда и блока, замена имени юзера. U0=`cat "$usersconf" | grep "Userdata0="`; Userdata0=( ${U0#Userdata0=} ) for element in ${Userdata0[@]}; do if [ ${element:0:8} == "username" ]; then UserName=${element##*[:=]}; continue; fi if [ ${element:0:3} == "tcp" ]; then tcplist=${element##*[:=]}; continue; fi if [ ${element:0:3} == "udp" ]; then udplist=${element##*[:=]}; continue; fi if [ ${element:0:9} == "rejecttcp" ]; then tcprejectlist=${element##*[:=]}; continue; fi if [ ${element:0:9} == "rejectudp" ]; then udprejectlist=${element##*[:=]}; continue; fi done # коррекция пути к домашнему каталогу юзера if [ "$UserName" != "" ]; then portforward=${portforward//$LOGIN/$UserName} fi # парсинг файла в домашнем каталоге юзера с заменой того, что напарсили в Userdata0 IFS="${CR}${LF}${TAB}" if [ -e "$portforward" ] ; then unset tcplist unset udplist while read U0; do Userdata0=( $U0 ) for element in ${Userdata0[@]}; do if [ ${element:0:3} == "tcp" ]; then tcplist=${element##*[:=]}; continue; fi if [ ${element:0:3} == "udp" ]; then udplist=${element##*[:=]}; continue; fi done done <$portforward fi # сделать массивы портов, ограничить по количеству, проверить диапазон портов IFS=".,; " declare -i k arr=( $tcplist ) # массив всех запрошенных портов declare -a ReqTcp=( ${arr[@]:0:15} ) # но берём только 16 первых k=${#ReqTcp[@]} for ((i=0; i<$k; i++)); do # проверка на попадание в заданный диапазон if [[ "${ReqTcp[$i]}" -gt "$PortMax" || "${ReqTcp[$i]}" -lt "$PortMin" ]]; then unset ReqTcp[$i] fi done arr=( $udplist ) declare -a ReqUdp=( ${arr[@]:0:15} ) k=${#ReqUdp[@]} for ((i=0; i<$k; i++)); do if [[ "${ReqUdp[$i]}" -ge "$PortMax" || "${ReqUdp[$i]}" -le "$PortMin" ]]; then unset ReqUdp[$i] fi done # то же для блокировки портов, но без ограничений arr=( $tcprejectlist ) declare -a ReqTcpReject=( ${arr[@]:0} ) k=${#ReqTcpReject[@]} for ((i=0; i<$k; i++)); do if [[ "${ReqTcpReject[$i]}" -ge "$PortMax" || "${ReqTcpReject[$i]}" -le "1" ]]; then unset ReqUdp[$i] fi done arr=( $udprejectlist ) declare -a ReqUdpReject=( ${arr[@]:0} ) k=${#ReqUdpReject[@]} for ((i=0; i<$k; i++)); do if [[ "${ReqUdpReject[$i]}" -ge "$PortMax" || "${ReqUdpReject[$i]}" -le "1" ]]; then unset ReqUdp[$i] fi done # теперь получим массивы портов, протоколов и адресов, уже занятые другими declare -a Port declare -a Prot declare -a Addr IFS=$CR i=0 for s1 in `$IPT -nt nat -L PREROUTING|cut -d" " -f1|grep PREROUTING`; do for s2 in `$IPT -nt nat -L $s1|grep DNAT`; do IFS=" :" arr=( `echo $s2` ) Prot[$i]=${arr[1]} Port[$i]=",${arr[7]}," # обрамить запятыми для упрощения анализа потом Addr[$i]=${arr[9]} ((i++)) done done #теперь получим массивы портов, которые слушает сервер IFS=$CR declare -a ListenTcp declare -a ListenUdp t=0; u=0 for s in `netstat -utln`; do IFS=" :" arr=( $s ) if [ ${arr[0]} == "tcp" ]; then ListenTcp[$t]=${arr[4]} ((t++)) elif [ ${arr[0]} == "udp" ]; then ListenUdp[$u]=${arr[4]} ((u++)) fi done #теперь проверить, чтоб запрос не попадал в списки ListenXXX #результат проверки - массив пересекающихся портов и удаление из запроса этих портов declare -a TcpDeny=( ) declare -a UdpDeny=( ) k=${#ReqTcp[@]} for ((i=0; i<$k; i++)); do for port in ${ListenTcp[@]}; do if [ "${ReqTcp[$i]}" == "$port" ]; then TcpDeny[${#TcpDeny[@]}]="-${ReqTcp[$i]}" unset ReqTcp[$i] fi done done k=${#ReqUdp[@]} for ((i=0; i<$k; i++)); do for port in ${ListenUdp[@]}; do if [ "${ReqUdp[$i]}" == "$port" ]; then UdpDeny[${#UdpDeny[@]}]="-${ReqUdp[$i]}" unset ReqUdp[$i] fi done done for ((i=0; i<${#Port[@]}; i++)); do case ${Prot[$i]} in "tcp") for port in ${ReqTcp[@]}; do if [ "${Port[$i]//,$port,/}" != "${Port[$i]}" ]; then TcpDeny[${#TcpDeny[@]}]=$port fi done ;; "udp") for port in ${ReqUdp[@]}; do if [ "${Port[$i]//,$port,/}" == "${Port[$i]}" ]; then UdpDeny[${#UdpDeny[@]}]=$port fi done ;; esac done # Сформируем строку перекрытых портов для отсылки сообщения DenyStr="" if [ ${#TcpDeny[@]} -gt 0 ]; then DenyStr=`echo " tcp(${TcpDeny[@]})"` fi if [ ${#UdpDeny[@]} -gt 0 ]; then DenyStr=$DenyStr`echo " udp(${UdpDeny[@]})"` fi if [ "$DenyStr" != "" ]; then DenyStr="Deny ports -"$DenyStr echo "$sgconf -s 127.0.0.1 -p 5555 -a messenger -w free -u $LOGIN -m \"$DenyStr\"" | at now+1minutes 2>/dev/null fi # Анализ портов закончен, начинаем подключать # Сделать юзерскую цепочку в таблице nat (если нету) if $IPT -t nat -N "PREROUTING_$LOGIN" 2>/dev/null; then if [ "${#ReqTcp[@]}" -gt 0 ]; then $IPT -t nat -A PREROUTING_$LOGIN -i ppp+ -p tcp -m multiport \ --dport `echo ${ReqTcp[@]}|tr " " ","` -j DNAT --to-destination $IP fi if [ "${#ReqUdp[@]}" -gt 0 ]; then $IPT -t nat -A PREROUTING_$LOGIN -i ppp+ -p udp -m multiport \ --dport `echo ${ReqUdp[@]}|tr " " ","` -j DNAT --to-destination $IP fi $IPT -t nat -A PREROUTING -j PREROUTING_$LOGIN fi # Сделаем юзерскую цепочку в таблице filter (если нету) if $IPT -N "BILL_$LOGIN" 2>/dev/null; then # тут режем порты назначения for ((i=0; i<${#ReqTcpReject[@]}; i++)); do $IPT -A "BILL_$LOGIN" -p tcp --dport ${ReqTcpReject[$i]} -j REJECT done for ((i=0; i<${#ReqUdpReject[@]}; i++)); do $IPT -A "BILL_$LOGIN" -p udp --dport ${ReqUdpReject[$i]} -j REJECT done # далее извлекаем тарифную скорость TariffSpeed=${TariffName#*-*} # параметр скорости из тарифа TariffSpeed=${TariffSpeed#-} # убираем лишний "-" if [ $TariffSpeed -gt 0 ]; then # если задана скорость > 0 # простое ограничение скорости limit="-m limit --limit ${TariffSpeed}/s --limit-burst ${TariffSpeed}0" fi echo -e " TariffName='$TariffName'\n TariffSpeed='$TariffSpeed'\n limit='$limit'" >/etc/stargazer/onconnect.log # фильтры для внешней локальной сети без ограничений скорости. # TODO Сделать загрузку правил из внешнего файла. Продумать формат этого файла. $IPT -A "BILL_$LOGIN" -s 10.0.0.0/8 -d $IP -j ACCEPT # IP нужен т.к. моя LAN 10.100.0.0/16 $IPT -A "BILL_$LOGIN" -d 10.0.0.0/8 -s $IP -j ACCEPT # а WAN 10.0.0.0/8 $IPT -A "BILL_$LOGIN" -s 85.21.29.0/24 -j ACCEPT # далее IP можно не упоминать $IPT -A "BILL_$LOGIN" -d 85.21.28.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -s 85.21.52.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -d 85.21.52.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -s 85.21.79.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -d 85.21.79.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -s 85.21.88.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -d 85.21.88.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -s 85.21.90.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -d 85.21.90.0/24 -j ACCEPT $IPT -A "BILL_$LOGIN" -s 83.102.146.0/24 -j ACCEPT # всё остальное (инет) ограничиваем по скорости $IPT -A "BILL_$LOGIN" -s $IP $limit -j ACCEPT $IPT -A "BILL_$LOGIN" -d $IP $limit -j ACCEPT fi # И наконец разрешить прохождение пакетов для юзера $IPT -I BILL -d $IP -j "BILL_$LOGIN" && \ $IPT -I BILL -s $IP -j "BILL_$LOGIN" # turn on internet echo "$D Local login='$LOGIN' ip=$IP cash=$CASH" >> $logfile # Уведомления об оплате # Проверим, что юзер платит абонплату (в названии его тарифа есть "abon_key или anlim_key") if [ "x${TariffName#$abon_key}" != "x${TariffName}" -o \ "x${TariffName#$anlim_key}" != "x${TariffName}" ]; then if [ $Day -ge $(( ${DinM[$Month]}-3 )) ]; then # осталось меньше 3-х дней Fee=$(cat $tariffs/$TariffName.tf | grep "Fee=" | cut -d"=" -f2) # Узнаем абонплату if [ $(echo "$CASH < $Fee" | $bc) != 0 ]; then # сравнение нецелых чисел echo "$sgconf -s 127.0.0.1 -p 5555 -a messenger -w free \ -u $LOGIN -m 'бМХЛЮМХЕ! с бЮЯ МЕ НОКЮВЕМ ЯКЕДСЧЫХИ ЛЕЯЪЖ. \ рЮПХТ: $Fee p.; нЯРЮРНЙ: $CASH p.'" | at now+1minutes 2>/dev/null fi fi # Если абонплата платится, включение на резервном роутере # Проверка доступности GW парой быстрых пингов if [ `ping -A -c 2 $GW2 >/dev/null;echo $?` != "0" ]; then echo "$D Error! Gateway $GW2 is inaccessible." >> $logfile # уведомление о недоступности резерва $sgconf -s 127.0.0.1 -p 5555 -a messenger -w free -u $LOGIN -m "хГБХМХРЕ, МН ЬКЧГ $GW2 МЕДНЯРСОЕМ. яБЪФХРЕЯЭ Я дЮМХКНИ тХПЯНБШЛ." exit fi # Проверим, что баланс > 0 cash=`cat "$usersstat" | grep "Cash=" | grep "-"` # если на абонплате и не в минусе или тариф анлимный if [ -z "$cach" -o "x${TariffName#$anlim_key}" != "x${TariffName}" ] ; then $GW2_ALLOW $IP # внешний скрипт управления резерным роутером echo "$D GW2_ALLOW $IP" >>/etc/stargazer/GW2.log # for debug fi fi