Введение.
---------
Как известно устройства Cisco позволяют фильтровать исходящие HTTP
запросы (команды filter url и url-server), посылая запрашиваемые URL
на внешний сервер для проверки. Но поддерживаются только коммерческие
продукты фирм Websensc (http://www.websense.com/) и N2H2 (http://www.n2h2.com/).
Но если задача только обрезать баннеры и не пустить людей на порно
сайты, можно попытаться сделать это своими руками.
Анализ протокола.
Был выбран продукт Sentian от N2H2, и UDP протокол. Сервер Sentian
использует порт 4005.
Протокол довольно простой, в нем есть 2 фазы: проверка того, что
сервер жив и собственно проверка запрошенного URL.
Формат пакетов.
Все передаваемые пакеты начинаются с 2-х байтного идентификатора типа.
Следующие 4-е байта - номер пакета, в каждом запросе номер уникален, а
в ответном пакете он совпадает с номером из запроса. Далее идут
данные, про которые поговорим позже.
Проверка доступности сервера.
Cisco периодически (определяется параметром timeout команды
url-server) посылает alive запрос.
|Длина Байт |Описание |Значение
2 идентификатор типа (запрос URL-а) 02 03
4 Номер запроса Уникально
14 В alive-запросе эта часть всегда
одинаковая и не используется. 00 00 00 00 00 00
00 00 00 01 00 01
00 00
Пример.
02 03 Идентификатор типа
00 01 78 24 Номер запроса
00 00 00 00 00 00 00 00 00 Данные
01 00 01 00 00
Сервер на alive-request отвечает alive-response пакетом
|Длина Байт |Описание |Значение
2 Идентификатор типа (запрос URL-а) 03 02
4 Номер запроса на который отвечаем
Совпадает с номером запроса
4 В alive-ответе эта часть всегда 00 00 00 00
одинаковая.
Пример.
03 02 Идентификатор типа
00 01 78 24 Номер запроса
00 00 00 00 Данные
Проверка запрошенного URL.
Когда пользователь открывает URL, Cisco пересылает на сервер IP адрес
и имя клиента, IP и URL запрошенного сервера.
|Длина Байт |Описание |Значение
2 идентификатор типа (запрос URL-а) 02 00
4 Номер запроса Уникально
4 IP клиента
4 IP запрошенного сервера
2 Длинна URL с завершающим 0
2 Длинна имени с завершающим 0
~Переменная Запрошенный URL Строка заканчивающаяся 0
~Переменная Имя пользователя Строка заканчивающаяся 0
Пример.
02 00 Идентификатор типа
00 01 78 25 Номер запроса
AC 12 0A 01 IP клиента
C2 55 22 E2 IP сервера
00 17 Длина URL
00 09 Длина имени
68 74 74 70 3A 2F 2F 77 Запрошенный URL: http://www.opennet.ru/
77 77 2E 6F 70 65 6E 6E
65 74 2E 72 75 2F 00
73 6F 6B 6F 6C 6F 66 66 Имя пользователя: sokoloff
00
В ответ сервер может послать либо разрешающий, либо запрещающий пакет.
Разрешающий пакет очень прост:
|Длина Байт |Описание |Значение
2 идентификатор типа (запрос URL-а) 00 02
4 Номер запроса на который отвечаем Совпадает с номером запроса
4 Ответ 00 00 00 00
Пример.
02 00 Идентификатор типа
00 01 78 25 Номер запроса
00 00 00 00 Данные
Теперь самое интересное, запрещающий пакет.
|Длина Байт. |Описание |Значение
2 идентификатор типа (запрос URL-а) 01 02
4 Номер запроса на который отвечаем Совпадает с номером запроса
4 Длинна URL
~Переменная URL на который перенаправляем клиента Строка заканчивающаяся 0
Пример.
01 02 Идентификатор типа
00 01 78 25 Номер запроса
00 00 00 1D Длина URL
68 74 74 70 3A 2F 2F 6C URL на который перенаправляем: http://localserver/stop.html
6F 63 61 6C 73 65 72 76
65 72 2F 73 74 6F 70 2E
68 74 6D 6C 00
Теперь клиент вместо запрошенной страницы увидит
http://localserver/stop.html. Очень похоже на работу баерорезалки в SQUID.
Пример реализации.
Пишем на perl простенький скрипт.
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $REQ_ALIVE=0x0203;
my $RES_ALIVE=0x0302;
my $REQ_REQUEST=0x0200;
my $RES_ACCEPT=0x0002;
my $RES_DENIED=0x0102;
my @banners;
open (FILE, `/root/banners.txt`) || die "Can`t open file";
while(){
chomp;
push(@banners, $_);
}
close FILE;
&main;
sub checkurl{
my $url=$_[0];
foreach (@banners){
if ($url=~/$_/i,) { return `http://localserver/stop.html`};
};
return ``;
};
sub main{
my $socket= IO::Socket::INET->new(Proto=>`udp`, LocalPort=>4005);
my $request;
while ($socket->recv($request, 4096)){
my($code,$id,$data)=unpack("nNa*", $request);
if ($code==$REQ_ALIVE) {
$socket->send(pack("nNN",$RES_ALIVE,$id,0)); # Посылаем ответ, что мы живые
}
elsif($code==$REQ_REQUEST){
my($cl_ip1,$cl_ip2,$cl_ip3,$cl_ip4, $serv_ip1,$serv_ip2,$serv_ip3,
$serv_ip4, $url_len, $name_len, $url, $name)=unpack("CCCCCCCCnnZ*Z*",$data);
if (my $replace=&checkurl($url)) {
print STDERR "$replace\n";
$socket->send(pack("nNN",$RES_DENIED,$id,length("$replace\x00")) .
"$replace\x00"); # В ответе посылаем URL куда перенаправить.
}
else{
$socket->send(pack("nNN",$RES_ACCEPT,$id,0)); # Посылаем ответ, что можно
};
};
};
};
В данном примере проверяется только URL ($url), но можно проверять IP
сервера($serv_ip1 - $serv_ip4), IP клиента ($cl_ip1 - $cl_ip4), имя
клиента ($name).
На Cisco выполняем команды:
url-server (inside) vendor n2h2 host 192.168.0.2 port 4005 timeout 5 protocol UDP
filter url http 0.0.0.0 0.0.0.0 0.0.0.0 0.0.0.0 allow
Здесь 192.168.0.2 - адрес машины где работает скрипт.
Вот и все.
2004. Соколов Александр. sokoloff@sendmail.ru