пятница, 30 декабря 2016 г.

В чем разница между CACHE HIT и BYTE HIT

Мои маленькие мохнатые друзья, поскольку меня уже сугубо задолбало объяснять, в чем заключаются различия между CACHE HIT и BYTE HIT применительно к различным системам кэширования - язык до крови стер и на пальцах кровавые мозоли - пришла пора на пальцах объяснить между ними разницу и раз и навсегда закрыть эту тему.

CACHE HIT

Данный показатель является значимым в следующих случаях:
  1. Размер кэшируемых объектов (единиц кэширования) одинаков или очень близок. Пример: страницы оперативной памяти, кластеры файловой системы, блоки базы данных.
  2. Размер кэшируемых объектов не имеет значения с точки зрения конкретной подсистемы и конкретного показателя эффективности этой системы.  Пример: Кэш DNS.
Фактическая расчетная формула для показателя:

(Количество единиц кэширования, давших HIT/Общее количество запрошенных единиц кэширования)*100

BYTE HIT

Данный показатель значим в одной и только одной ситуации:
  1. Размер кэшируемых объектов значительно отличается (на порядок и более). Пример: Прямой или реверсивный кэш веб-контента.
Фактическая расчетная формула для показателя:

(Объем единиц кэширования в байтах, давших HIT/ Общий объем запрошенных единиц кэширования)*100

Примеры и выводы

Конкретные пример на пальцах. 

У вас есть 10 файлов по 1 Кб. CACHE HIT равен 90%. Это значит, что вы кэшировали 9 Кб, а для 1 Кб зафиксирован промах. Это означает, что эффективность кэширования в точности равна 90%. Для этого же случая - если BYTE HIT равен 90%, это означает, что 9 Кб попали в кэш и дали HIT, а 1 KB - нет. Очевидно, что в данном конкретном случае показатели CACHE HIT и BYTE HIT идентичны. То есть, если размер единиц кэширование идентичен, показатели совпадают и в применении показателя BYTE HIT нет смысла.

Обратный пример.

У вас есть 9 файлов по 1 Кб, и 1 файл 1 Гб. CACHE HIT равен 90%, при этом закэшированы файлы 1 Кб, а файл 1 Гб - нет. Что это означает? Это означает, что фактическая эффективность вашего кэша - нет, не 90% - а менее 1%. 9 Кб вы взяли из кэша, а 1 Гб - нет. В этом же примере, если BYTE HIT равен 99%, то есть файл 1 Гб дал HIT, а 9 файлов по 1 Кб дали MISS, то эффективность превосходна, несмотря на то, что CACHE HIT в этом случае будет 10%. Таким образом, чем больше разброс величин единиц кэширования, тем больше вероятность того, что корреляция между CACHE HIT и BYTE HIT будет в большей степени стремиться в разные стороны.

Я не говорю, что вышесказанное - закон природы. Однако это закономерность, характерная для приведенных выше примеров.

Что следует из вышесказанного?

Из вышесказанного следует, что, в случае, скажем, кэширующего прокси, CACHE HIT как таковой, ничего общего с эффективностью работы не имеет. От слова "совсем". И единственным показателем в этом случае является только BYTE HIT. Поскольку чем он выше, тем меньше данных мы запрашиваем из интернета (по объему) и тем больше получаем их из кэша.

Q.E.D.

вторник, 27 декабря 2016 г.

15 признаков того, что вы - гнуторас

1. Вы знаете основные отличия свободных и частично свободных лицензий друг от друга. Вы понимаете, чем отличается MIT от BSD, и обе они от GPL. O GPLv3 вы мало что знаете, кроме того, что она частично рестриктивная, а EULA для вас совершенно темный лес- вы никогда ее не читали дальше преамбулы, но она - порожденье Сатаны Гейтса. :)

2. Вы всегда требуете исходники программы, хотя не понимаете на С, или, тем более, на С++, ни-че-го. Передаю по буквам - Николай-Иван-Харитон-Ульяна-Яков, ни-че-го. (Вы знаете, чем отличается мьютекс от мутанта и оба от спинлока? Правильно, я тоже не знаю :)) Зачем вам  тогда исходники? А хер его знает, швободка опять же, ну и пальцы погнуть перед окошечниками есть чем. :) Понимать код, а, тем более, читать перед сборкой совершенно не обязательно - правда же? - в него другие 10 миллионов мух глаз смотрят, может, хотя бы они фигу не видят и спасут человечество от дятла. :)

3. Ваш первый вопрос потерпевшему собрату - "Какую версию Линукс ты используешь и как ставишь софт из репов". (вариация для седого гнутораса 50+ - "Кто устанавливал тебе Линукс?" Операционные системы, не являющиеся Линукс, для вас не существуют - это чистой воды некрофилия и образцовое "ненужно", которое уже сдохло, "закопайте обратно".

4. Вы знаете, что означает RMS и вам пофигу, что оно жрет бородавку с ноги - оно бох. :)

5. Вам известно, что такое "палец Линуса животворящий", какой палец, кому и по какому поводу был показан. Вас не смущает полное отсутствие воспитания оного Линуса и смысл жеста - предельно похабный. Причем истинный смысл жеста вам не слишком известен.

6. Вы знаете, что означает "Ленарт Поттеринг" и вы знаете, почему он отличается от "Гарри Поттера". :) Фраза "Вышла версия 235 systemd" вызывает в вас праведный гнев, хотя лично вы не написали ничего, кроме программы на паскале для диплома в институте и пары простеньких скриптов на Bash в компании "Рога-и-Копыта-Инкорпорейтед", в которой работали после.

7. Кстати, о Bash. Вас охватывает священный гнев при словах "маловразумительная простыня на Bash", а при необходимости писать подобное - вас охватывает смесь ужаса, стыда и праведного гнева - "Я выше этого!" и "В 2016м не пишут на Баше!". Помимо того, что вы просто не были в состоянии освоить скриптинг на мало-мальски сносном уровне. А сейчас вы готовы скорей взять мельничный жернов типа Perl, лишь бы только не писать на шелле. Хоть на С - но Баш - не наш метод! По той же причине вы готовы скорей стать геем, чем использовать sysv init. Вы даже готовы наступить себе на глотку и использовать п.6, но не писать на шелле. Ни-ког-да.

8. Кстати, о скриптах. Хотя недоязыки программирования Perl, и Pyton - одинаково находятся за гранью добра и зла, вы миритесь с монструозностью Perl и готовы заживо спалить питонятников, пишущих то, что называете "поганая бидонятина". По мнению нормальных админов, интерпретируемые языки, требующие объектных библиотек для мало-мальски внятного функционала не имеют права на жизнь, но вы же - не нормальные админы, а на локалхосте эти языки та-акие уютненькие! :) Кстати, чтоб два раза не вставать - Lua тоже за гранью добра и зла - на нем пишут окошечники.

9. Вы знаете, что такое www.opennet.ru. Хотя этот местечковый междусобойчик тоже находится за гранью добра и зла, вы хотя бы раз в месяц его посещаете под тем предлогом, что "там можно узнать о новой версии гнутого софта или услышать об уязвимости раньше, чем придет рассылка". :) На самом деле вам просто нравиться читать срачи сектантов в каментах, а иногда и участвовать в перепалке местных анальных клоунов. Анонимно, разумеется.

10. Если случайно вам все же потребуется написать что-либо на С или С++, вы бежите на stackoverflov.com, задаете там панический вопрос, который местные гуру - которые уже давно не программируют, но кого это волнует? - сразу помечают как уже заданный овер 10 лет назад,
затем местные аборигены дают вам дюжину ответов, один из которых отсылает вас к древнему коду на С, два рекомендуют использовать Boost (для всего на свете, это такая омнибиблиотека, написанная геями-инопланетянами для геев-инопланетян), а пара новообращенных школоло
советуют воспользоваться instringstream, о чем бы не был вопрос - но кого это волнует? Случайно надыбав или получив правильный ответ методом угадывания и панической компиляции, вы уволакиваете код в свой уголок и с чистой совестью присваиваете как написанный лично вами. :)


PS. Шикарнейшая иллюстрация прилагается. Посмотрите, что за вопрос был задан, как оп уточняет, что желает решение с STL, и первый же ответ - use Boost, Luke! Мне всегда было интересно - эти мудаки-гуру вообще читать умеют, блеать? Это ведь не единственный подобный вопрос и не единственный подобный ответ. Или это они и есть те самые геи-инопланетяне? :)

Ну и чтобы два раза не вставать:

Пoртрет среднего пользователя Boost:

А это сама команда разработчиков Boost:

Во главе со своим тим-лидом, разумеется:

11. Вы считаете, что STL написали геи, но сами не написали ничего и даже не зарепортили ни одного по-настоящему серьезного бага. Хотя все время собираетесь.

12. Вы считаете, что Linux имеет прямое отношение к UNIX, более того, превосходит его во всем, и не знаете, кто сказал фразу "Linux is NOT UNIX".

13. Вы снисходительно относитесь к разработчикам СПО нетрадиционной ориентации, однако мерзко хихикаете, услышав название "Pidora".

14. Все пользователей Windows вы огульно считаете недоумками, способными лишь нажимать мышкой кнопки Next-->next->next->Finish. Однако вам не приходит в башку, что последовательность, которую вы повторяете минимум еженедельно: yum install - в общем-то, от окошечной ничем не отличается по существу. Да и вы, по сути, мало чем отличаетесь от пользователя винды. ;)

15. Вы знаете, кто такой Шаттлворт, чем плоха Красная Шапка (нет, она не минет Серому Волку плохо делала, отнюдь), и считаете, что Cisco, Oracle и до кучи Google - ось зла. Хотя вам все равно очень хочется работать в одной из них, получая деньги и показывая по вечерам фигу в кармане на форумах таких, как вы, сектантов, проповедуя им, что швобода - оно, конечно, хорошо, но швободно - не значит бесплатно, а презренный металл позволяет вам оплачивать счета в ожидании наступления всеобщего Коммунизма. :)

Если вы кивнули гривой хотя бы на 10 пунктов - срочно к психиатру, вам начинает промывать мозги секта под названием GNU. Если вы отметили в себе более 12 пунктов и обращались ранее к психиатру - обращайтесь снова, пусть он вам вернет деньги!

суббота, 3 декабря 2016 г.

C++: Разбивка строки на токены с составными разделителями и сохранением токенов и разделителей в векторе

Известно множество решений данной задачи. 

Однако довольно часто разбивка строки требуется несколько в более сложном варианте. А именно, требуется использовать составные разделители (более, чем один символ или пара одинаковых символов), и требуется в последовательности токенов сохранять разделители, например, для последующей обработки с заменой в качестве placeholders ну и тому подобное.

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

В этой связи библиотека регулярных выражений C++ достаточно эффективна.

Данный алгоритм является видоизменением хорошо известного опубликованного алгоритма.

Допустим, что у нас есть вот такая строка:

 Quick#1brown#2fox#3jump#4over#5lazy#6white#7rabbid  

в которой разделителем является сочетание символа # и цифр от 0 до 99.

Для решения вышеописанной задачи модифицируем алгоритм с использованием std::sregex_token_iterator, сохраняющий токены в vector:

 #include <iostream>  
 #include <vector>  
 #include <string>  
 #include <regex>  
   
 // Split string with save separators  
 std::vector<std::string> split(const std::string & s, std::string rgx_str = "\\#([\\d][\\d]?)+")  
 {  
      std::vector<std::string> elems;  
      std::regex rgx (rgx_str);  
   
      std::sregex_token_iterator iter(s.begin(), s.end(), rgx, -1);     // Get token  
      std::sregex_token_iterator iter1(s.begin(), s.end(), rgx, 0);     // Get separator  
      std::sregex_token_iterator end;  
   
      while (iter != end) {  
           elems.push_back(*iter);               // Put token  
           if (iter1 != end) {  
                elems.push_back(*iter1);     // Put separator if found  
                ++iter1;  
           }  
      ++iter;  
      }  
      return elems;  
 }  
   
 int main() {  
   std::string test_string = "Quick#1brown#2fox#3jump#4over#5lazy#6white#7rabbid";  
   std::cout << test_string << std::endl;  
   
   std::vector<std::string> tokens = split(test_string);  
   for (std::vector<std::string>::const_iterator it = tokens.begin();   
     it != tokens.end(); it++)  
       std::cout << *it << std::endl;  
 }  

Скомпилируем его и выполним:

 root @ khorne /tmp # g++ -O3 -std=c++11 -m64 -c -o split1.o split1.cc  
 root @ khorne /tmp # g++ -s -m64 -o split1 split1.o  
 root @ khorne /tmp # split1  
 Quick#1brown#2fox#3jump#4over#5lazy#6white#7rabbid  
 Quick  
 #1  
 brown  
 #2  
 fox  
 #3  
 jump  
 #4  
 over  
 #5  
 lazy  
 #6  
 white  
 #7  
 rabbid  
   

Теперь можно очень легко идентифицировать разделители и выполнить дальнейшую обработку.

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

вторник, 29 ноября 2016 г.

Squid: Как гарантированно удалить объект из кэша командой PURGE

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

Вы, наверное, и сами это видели.

Давайте проведем небольшой эксперимент с объектом, который гарантированно находится в кэше.

Как у любого нормального админа-практика, у нас есть алиас в шелле для purge:

  alias purge1="/usr/local/squid/bin/purge -p localhost:3128 -P 1 "  

Выполним его.


Код 200 - выполнился?

Попробуем выполнить команду еще раз.

Удалился:


Давайте попробуем другой объект.


Вот этот. Мы нашли его выполнением команды purge без параметра P - то есть только показать, но не удалять.

Объект явно в кэше, его размер 9017 байт.

Выполняем purge -P 1 и......


404! Не найден в кэше!

Хорошо, еще раз:


Какого дьявола! Мы удалили объект дважды, purge мало того, что показывает для объекта, присутствующего в кэше, 404, так еще и не удаляет его!

Внимательно перечитываем исходники purge. И видим, что, помимо того, что удаление объектов по методу PURGE может и не гарантироваться, также существует недокументированный режим принудительного удаление объектов, а именно -P 0xf.

Отлично, приготовим алиас и для этой команды:

  alias purgef="/usr/local/squid/bin/purge -p localhost:3128 -P 0xf "  

и выполним ее:


Вот на этот раз объект удален.

Можете забить на предупреждение:

WARNING! Caches files were removed. Please shut down your cache, remove
your swap.state files and restart your cache again, i.e. effictively do
a slow rebuild your cache! Otherwise your squid *will* choke!

Нам наплевать, что кэш будет в шоке ;) Кстати, предупреждение излишне. Кэшу от этого хуже не станет. Перестраивать swap.state нет необходимости.

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

Более того. Если вы используете squidclamav, который имеет функционал trust cache (посылающий команду PURGE при обнаружении malware) - то, как вы видели выше, удаление может фактически и не произойти. И malware может пойти из кэша дальше.

В любом случае, кто предупрежден - тот вооружен, а кто вооружен - тот почти бесстрашен. ;)

вторник, 1 ноября 2016 г.

DNSCrypt: блокирование запросов AAAA

Как это ни дико звучит для некоторых, однако есть целый ряд стран (или сетей), где IPv6 отсутствует как класс - не сконфигурирован, не используется, или не планируется к использованию.

Соответственно, некоторые виды программного обеспечения, которые, в соответствие с RFC (чтоб его черти драли, это рекомендации, а не догма) имеют неотключаемую поддержку IPv6 (или не рекомендуемую к отключению, в соответствии со все теми же, не к ночи помянутыми, RFC). 

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

Для подобных случаев существует возможность (да, я говорю о вынесенном в заголовок ПО - DNSCrypt) не выполнять запросы AAAA, а немедленно, без выполнения запросов, ответить пустой строкой.

Хочу подчеркнуть, что этого в принципе можно было бы добиться, например, отключением выполнения запросов IPv6 в Unbound, но это частный случай, а далеко не всякое ПО позволяет это проделать, и, кроме того, в случае с комбинацией Unbound+DNSCrypt это, как правило, не помогает.

Итак, как ликвидировать в случае "IPv4 only" данную проблему (смотрим на заголовок - для DNSCrypt)  и добиться повышения латентности, исключения отказа некоторых запросов итп.?

В DNSCrypt существуют плагины, один из которых, в частности, позволяет добиться указанной цели в одно действие (не обращаем внимания на слово example в названии плагинов - они вполне себе продуктивные) - libdcplugin_example_ldns_aaaa_blocking.

Плагин работает в точности по описанному выше принципу - на запрос AAAA он немедленно возвращает пустую строку, что предотвращает выполнение IPv6 DNS-запросов в среде, не предназначенной для передачи IPv6.

Обратите внимание - чтобы плагин (и связанная с ldns группа плагинов DNSCrypt)  собрался, требуется наличие установленной библиотеки libdns( и libdns-dev, разумеется).

Допустим, что все хорошо и после установки DNSCrypt мы имеем весь набор плагинов в /usr/local/lib/dnscrypt-proxy:

 root @ khorne / # ls -al /usr/local/lib/dnscrypt-proxy  
 total 75  
 drwxr-xr-x 2 root root  14 Nov 1 01:53 .  
 drwxr-xr-x 6 root root  78 Oct 26 20:57 ..  
 -rwxr-xr-x 1 root root 1069 Nov 1 01:53 libdcplugin_example.la  
 -rwxr-xr-x 1 root root 7184 Nov 1 01:53 libdcplugin_example.so  
 -rwxr-xr-x 1 root root 1190 Nov 1 01:53 libdcplugin_example_ldns_aaaa_blocking.la  
 -rwxr-xr-x 1 root root 7448 Nov 1 01:53 libdcplugin_example_ldns_aaaa_blocking.so  
 -rwxr-xr-x 1 root root 1160 Nov 1 01:53 libdcplugin_example_ldns_blocking.la  
 -rwxr-xr-x 1 root root 12520 Nov 1 01:53 libdcplugin_example_ldns_blocking.so  
 -rwxr-xr-x 1 root root 1208 Nov 1 01:53 libdcplugin_example_ldns_opendns_deviceid.la  
 -rwxr-xr-x 1 root root 8984 Nov 1 01:53 libdcplugin_example_ldns_opendns_deviceid.so  
 -rwxr-xr-x 1 root root 1238 Nov 1 01:53 libdcplugin_example_ldns_opendns_set_client_ip.la  
 -rwxr-xr-x 1 root root 9416 Nov 1 01:53 libdcplugin_example_ldns_opendns_set_client_ip.so  
 -rwxr-xr-x 1 root root 1117 Nov 1 01:53 libdcplugin_example_logging.la  
 -rwxr-xr-x 1 root root 8880 Nov 1 01:53 libdcplugin_example_logging.so  
   

Чтобы использовать вышеуказанный плагин (и, кстати, любой другой; кроме того, можно использовать и несколько плагинов одновременно), достаточно указать при запуске демона DNSCrypt по имени в соответствующем аргументе dnscrypt-proxy его la-файл:

 dnscrypt-proxy 1.7.0  
   
 Options:  
   
  -R  --resolver-name=...  
  -a  --local-address=...  
  -d  --daemonize  
  -E  --ephemeral-keys  
  -K  --client-key=...  
  -L  --resolvers-list=...  
  -l  --logfile=...  
  -m  --loglevel=...  
  -p  --pidfile=...  
  -X  --plugin=...  
  -N  --provider-name=...  
  -k  --provider-key=...  
  -r  --resolver-address=...  
  -S  --syslog  
  -Z  --syslog-prefix=...  
  -n  --max-active-requests=...  
  -u  --user=...  
  -t  --test=...  
  -T  --tcp-only  
  -e  --edns-payload-size=...  
  -I  --ignore-timestamps  
  -V  --version  
  -h  --help  
   
 Please consult the dnscrypt-proxy(8) man page for details.  
   

То есть:

 --plugin=libdcplugin_example_ldns_aaaa_blocking.la    

После перезапуска сервиса DNSCrypt можно убедиться, что указанный плагин загружен при помощи команды pldd:

 root @ khorne / # ps -ef|grep [d]nscrypt  
   root 8083   1  0 02:29:24 ?      0:02 /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_5555.pid   
   root 8071   1  0 02:29:24 ?      0:01 /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_5553.pid   
   root 8065   1  0 02:29:24 ?      0:01 /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_5552.pid   
   root 8077   1  0 02:29:24 ?      0:01 /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_5554.pid   
   root 8059   1  0 02:29:24 ?      0:02 /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_5551.pid   
 root @ khorne / # pldd 8083  
 8083:  /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/tmp/dnscrypt_ha_  
 /lib/amd64/libsendfile.so.1  
 /opt/csw/lib/amd64/libltdl.so.7.3.1  
 /usr/local/lib/libsodium.so.18.1.1  
 /usr/lib/amd64/libkvm.so.1  
 /lib/amd64/libsocket.so.1  
 /lib/amd64/libnsl.so.1  
 /lib/amd64/librt.so.1  
 /lib/amd64/libm.so.2  
 /opt/csw/lib/amd64/libssp.so.0.0.0  
 /lib/amd64/libpthread.so.1  
 /lib/amd64/libc.so.1  
 /opt/csw/lib/amd64/libgcc_s.so.1  
 /lib/amd64/libelf.so.1  
 /lib/amd64/libaio.so.1  
 /lib/amd64/libmd.so.1  
 /lib/amd64/libmp.so.2  
 /lib/amd64/libscf.so.1  
 /lib/amd64/libdoor.so.1  
 /lib/amd64/libuutil.so.1  
 /lib/amd64/libgen.so.1  
 /usr/local/lib/dnscrypt-proxy/libdcplugin_example_ldns_aaaa_blocking.so  
 /opt/csw/lib/amd64/libldns.so.1.6.17  
 /opt/csw/lib/amd64/libcrypto.so.1.0.0  
   

Не забудьте внести изменения в запускные скрипты/сервисы DNSCrypt.

Вот и все, folks!

среда, 12 октября 2016 г.

"Мой SHA длиннее твоего, Одинокая Звезда!"

или "Мальчик, который кричал "Волк!""

Я понимаю, когда обычные простые смертные не желают вникать в проблемы безопасности (даже поверхностно), выбирают пароли вида 12345, и заявляют, что они простые и честные маленькие людишки, которым нечего скрывать, даже своих грязных трусов.

Но вот чего я совершенно никогда не пойму, так это специалистов и профессионалов IT, которые почему-то считают, что Let's Encrypt и всеобщий HTTPS хоть как-нибудь спасут и сохранит. От АНБ, КГБ и любых других трехбуквенных заведений мира.
  1. При слабых паролях вас вообще никто и никак не спасет. Если у сарая нет задней стены, прочность его ворот не имеет значения.
  2. В случае чего-либо хоть сколько-нибудь критичного - поздно боржоми пить и шифровать весь front-end. Конюшню не запирают когда лошадь уже поимели. Это хоть кто-то понимает? К вам со служебного входа заходит КГБшник, с ноги открывает дверь, требует отдать - и вы послушно раздвигаете ноги и отдаетесь. Сечете? К чему этот цирк шапито с клиентским якобы-шифрованием? Вы кого, собственно, пытаетесь обмануть, недоумки?
А теперь - внимание, вопрос века.

Что и от кого пытаются спрятать новостные сайты СНГ? Государственные секретики? Вам что шифровать? Или вы пытаетесь убедить пользователей, что у вас безопасненько а вы не раздвинете ноги по первому требованию?

Вот это - что?



Ах, это вы в поисковой выдаче Гугла пытаетесь подняться....

А теперь смотрите сюда:

BBC

CNN

New-York Times

Как говорится, почувствуйте разницу! Вы - не WikiLeaks, не социальные сети с логином на каждой странице, не сайт Брюса Шнайера! Вы вообще никто и звать вас никак (вам до BBC/CNN/NY Times как до Пекина раком) и ни единого секретика не содержите в принципе, никакой ценности для разведок и спецслужб не представляете - они к вам ногами в состоянии прийти, с ноги открыть дверь и вы как миленькие перед ними ляжете и, как я уже сказал выше, раздвинете ноги. Так какого черта вы обвешались замочками? Кого вы морочите? Дилетантов?

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

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

Я приведу пример - вы, выходя на улицу, всегда одеваете бронежилет, каску, сапоги, берете с собой пистолет и резиновую дубинку? А когда в гости идете? На свидание? И на улице кругом одни бойницы, бронедвери, колючая проволока, сторожевые вышки?

А теперь вопрос. От того, что это не так - мир стал хуже? Менее безопасным?

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

Иллюзия безопасности много хуже полного ее отсутствия. А вы именно это и создаете. Иллюзию.

Для самых тупоголовых я объясню совсем на пальцах.

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

От целенаправленного взлома зеленый замочек не защищает ни меня, ни вас. Ни Эдварда Сноудена, если уж на то пошло.

Вспомните HeartBleed, неучи. Когда из-за рождественского ПМС разработчика всемирно известной криптобиблиотеки полмира с зеленым замочком просто имели три дня в задницы. А крупные компании с зелеными замочками не мычали и не телились, пока уже совсем невмоготу не стало терпеть.

Этот пепел должен стучать в ваши задницы, раз уж в головы он достучаться не смог.

Знаете, что вы сделали, недоумки? Вы нашли Самую Большую Пробку. И увеличили площадь поражения до размеров таких, что в нее даже самый слепой и однорукий хакер Вася не промахнется.

вторник, 11 октября 2016 г.

Brocade 200: Zoning

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

Это не совсем верно.

Зонирование позволяет разграничить трафик четко заданными направлениями, исключить ASIC saturation и, по-существу, избавиться от работы фабрики в режиме хаба (что может в некоторых случаях привести к возникновению узкого места непосредственно на фабрике).

Итак, задача. 

Есть старенький, но исправный и мало проработавший Brocade-200E. (Кстати, во многих отношениях он заметно превосходит 300 модель). Прошивка (FabricOS) - 6.2.2.0, последняя из существующих для данной модели.

Задача. 

Имеется хост, с двумя HBA. Имеется две дисковых полки с двумя контроллерами, в режиме Active-Passive. На хосте включен multipathing. Нужно построить правильный зонинг свича, с учетом того факта, что хост не имеет виртуализации, и на нем крутится IO-интенсивное приложение, использующее обе дисковых полки. Имеется Brocade-200E в дефолтной конфигурации со всеми необходимыми лицензиями.

Неожиданно обнаружилось, что документация в интернет, мягко говоря, неточна.

Для FabricOS 6.2.2.0 нельзя определить алиасы.

Как правильно построить зонинг для вышеуказанной задачи, с учетом особенностей конфигурирования и функционирования составных частей SAN?

Правильный ответ:

 Brocade 200E zoning  
 -------------------  
 ## 0-1 - host, 2-3 - A3510, 4-5 - 2540M2  
  0  0  id  N2  Online      F-Port 10:00:00:00:c9:50:86:44   
  1  1  id  N2  Online      F-Port 10:00:00:00:c9:50:86:13   
  2  2  id  N2  Online      F-Port 21:60:00:c0:ff:88:6b:8a   
  3  3  id  N2  Online      F-Port 22:60:00:c0:ff:a8:6b:8a   
  4  4  id  N4  Online      F-Port 20:36:00:80:e5:2f:64:70  
  5  5  id  N4  Online      F-Port 20:47:00:80:e5:2f:64:70   
   
 # zonecreate "zone1", "10:00:00:00:c9:50:86:44; 21:60:00:c0:ff:88:6b:8a; 20:36:00:80:e5:2f:64:70"  
 # zonecreate "zone2", "10:00:00:00:c9:50:86:13; 22:60:00:c0:ff:a8:6b:8a; 20:47:00:80:e5:2f:64:70"  
   
 # cfgcreate "CacheServer", "zone1; zone2"  
   
 # cfgenable "CacheServer"  

То есть, зоны определены физические, по WWN (иначе не получится, алиасы, как я сказал выше, в 6.2.2.0 определены быть не могут. Команда проходит без ошибки, но зоны не создаются).

Созданы две зоны (см.постановку задачи выше), объединены в конфигурацию, конфигурация активирована.

Осталась мелочь. Надо изменить режим оптимизации фабрики:

 # aptpolicy 1  
 (defailt is 3.0)  

и перезапустить фабрику (внимание, трафик устройств будет прерван!)

среда, 5 октября 2016 г.

Кто устережет сторожей - 2

Первая часть Марлезонского балета или Почему Большой Брат не имеет никакого смысла кроме распилов и откатов

Регулярный зуд в заднице тоталитарных государств на тему тотального шпионажа за собственными гражданами, которых оные государства до смерти боятся, уже не вызывает ничего, кроме желания сказать с интонациями Онакена Скайуокера "Ну, попробуй!".

Дуракам бесполезно говорить, что они дураки. Тем более - объяснять, почему это так. До тех пор, пока они сами не расшибут себе лоб об бетонную стенку с летальным исходом.

  • Я скажу чуть больше, чем первоначально намеревался. По моему глубокому убеждению, любой человек, выдвигающий подобную идею или помогающий ее реализовать - является государственным преступником и прямой, непосредственной угрозой государственной безопасности и цифровому суверенитету страны и должен сидеть под замком в крепкой одинокой камере пожизненно. Фарш невозможно провернуть назад, даже единожды сделанная, такая попытка приведет к экономическому коллапсу - в современных условиях - вследствие колоссального роста преступности в отношении граждан и учреждений; не мгновенно, но тем не менее, неотвратимо; и это столь же опасно, как и создающееся ядерное оружие Северной Кореи. Я ни одной секунды не верю ни в чистоту рук спецслужб, ни в их техническую компетентность и способность обеспечить хоть сколько-нибудь надежную защиту подобных массивов конфиденциальных данных - сомневающимся советую вспомнить имена "Ассандж" и "Сноуден". Так же, как советую пастве, радостно одобряющей подобные затеи и заявляющей, что спецслужбы не интересуют маленькие грязные секреты маленьких людишек, подумать о том, хотели бы они, чтобы ВСЯ их СМС переписка была показана их жене, их начальнику или хотя бы озвучена в суде. Зная реальную жизнь не из телевизора, могу с уверенностью сказать, что такие данные немедленно окажутся как минимум в руках хакеров, а как максимум - будут слиты или утрачены спецслужбами либо будут использоваться непосредственно спецслужбами против граждан - и после этого будет поздно дергаться и пить боржоми. И это будет несравненно хуже, чем международный терроризм, потому что открывает возможности для масштабных информационных (и не только) терактов. Если вам это почему-либо не очевидно, поинтересуйтесь новостями о киберпреступлениях хотя бы года так с 2008го. Именно в этом году оборот от киберпреступности превысил оборот наркобизнеса.

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

Допустим - чисто гипотетически, потому что затрахаетесь пишется через большую и жирную буковку Ё - что вы смогли-таки получить пресловутые Ключи-От-Интернета и смогли взять под контроль все и всяческие коммуникации - социальные сети, и, что представляет для вас наибольший интерес - все без исключения (да-да, я говорю и про Bitmessage, и про Tor и про SSH с VPN - чего даже АНБ не смогли, а там значительно более умные и образованные люди работают, чем вы, господа) мессенджеры. И даже начали копить всю переписку, все логины и пароли граждан и их маленькие грязные секретики.

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

Называется решение - подстановочный шифр.

- Бабушка, вышли племяннице маслят в ее день рожденья.
- Да, Коленька, скажи если надо игрушку положить в посылку.

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

Вот и все, ребята. И вся ваша навороченная система шпионажа начинает сосать не наклоняясь.

Дам ценный совет. Организационные проблемы техническими методами не решаются. Это - аксиома, известная любому мало-мальски грамотному инженеру или аналитику. Да, я знаю, что среди вас таких отродясь не водилось. Однако решение существует. Запретить все коммуникации. Вообще. Все, включая телефон и бумажную почту. 25 лет тюрьмы за попытку коммуникации между людьми. Запретить Интернет. И добро пожаловать в XIII век, страна! (Но нам же на это наплевать, убедить только шишка на собственном лбу в состоянии, да и то не всех и не всегда) Я, разумеется утрирую. Чесаться, если совсем по-хорошему, следовало начать значительно раньше. Дальновидные люди, вроде того же АНБ, это и начали делать еще тогда, 20 лет назад. У остальной части мира шея длинная а резьба оказалась мелкая, дальновидность равна отрицательной величине. А сейчас уже тупо поздно дергаться. В человеческой истории щит еще никогда не победил меч.

Такие дела. Желаю удачи в шпионаже за населением.

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


Вторая часть Марлезонского балета - хранение Big Data или в чем разница между зеттабайтом и квадратным литром. С пробочкой.

Одно из государств, жаждущих построить такую систему, желает хранить все данные. Ну, если не вспоминать о факте, что данное государство никогда не производило в промышленных количествах жестких дисков приличного класса, а Seagate с Western Digital который месяц пьют до синевы стоя за такого мегазаказчика, не вспоминать о том, что в куче из ста миллионов жестких дисков по определению будут каждую минуту выходить из строя несколько десятков устройств хранения, что зеттабайт - это не квадратный литр с пробочкой, а величина квантовых порядков, что каким-то образом надо будет обеспечить защиту данных (в случае, если удастся их собрать) по классу B Оранжевой книги как минимум - то все отлично. 

Да, и кстати, вы должны быть в теме, что на планете Земля существует на данный момент лишь одна файловая система, способная хранить зеттабайты. Джефф Бонвик смотрит на вас свирепо, грустно, и в то же время с недоумением.

WD, Seagate, Hitachi допиваются до белочки в финале, а вы имеете астрономическую кучу данных. Может быть. В Заполярье. Целая Якутия в вашем распоряжении для строительства датацентра в вечной мерзлоте (для охлаждения) и Красноярская+Братская ГЭС для его питания (должно хватить). Посчитайте на калькуляторе на досуге, сколько дисков объемом 1 Тб каждый вам потребуется для хранения 1 зеттабайта. Умножьте на стоимость одного диска. Уверены, что хватит ВВП?

Кроме того. Вы хорошо подумали, что и, главное, как вы будете там искать? Собрать-то мало. Надо еще там преступников найти. Каким-то образом отделив агнцов от козлищ. И перечитайте первую часть статьи.

Другая, чуть более умная страна, произнесла слово Big Data.

Окей, вы в курсе, что называют Big Data? Это неструктурированная свалка неструктурированных данных. Потому что в реляционные БД такие данные засунуть не получиться - их слишком много. Вспомните Yahoo с их самой большой на планете аналитической БД размером в 6 петабайт. (это тоже не квадратный литр с пробочкой) Прошу не забывать, что для этого им пришлось полностью переписать PostgreSQL.

Теоретически вы могли бы затолкать данные в реляционную БД. Теоретически - потому что две самых крутых БД на планете фактически больше нескольких экзабайт реально хранить не способны. Да еще надо дьявольски потрудиться с физическим размещением этих данных - снова вспоминаем допившихся уже досиня сотрудников Seagate, WD, Hitachi....

И - да, чтобы что-то искать в Big Data - надо еще более сильно постараться, надо знать, что именно искать, кроме того, что надо помнить тот факт, что обычно Big Data обрабатываются статистически, а точечных результатов от них по определению не ждут и ждать не могут. И знать это желательно на берегу, а не тогда, когда куча данных уже собрана.

Одна маленькая, но гордая страна, желающая бороться с преступниками подобным образом, решила собирать логи доступа с минимальными данными - кто и куда ходил. IP и IP. И год их хранить. Эта маленькая, но гордая страна компрессирует логи. И потом может в них найти данные лишь постфактум. Естественно, ни о какой поимке кого бы то ни было на основе простых метаданных соединений и посещений речи быть не может. Что, в общем, понятно и ежу - в петабайтах плоских текстовых логов особо не поищешь. Но Seagate, WD и Hitachi за их здоровье тоже пьют будь здоров (я все жду, когда у них печень не выдержит). А это, напоминаю, маленькая, но гордая страна - с петабайтами простых упрощенных логов. Которая ни одного преступника подобным образом не поймала, кстати. Хотя пытается. Но уже не слишком решительно.

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

Третья часть Марлезонского балета - найти и уничтожить

Чисто гипотетически - допустим, построили. Собрали. 

Теперь - что ищем-то? И как будем искать? В свалке неструктурированных произвольных данных? Ну ладно - допустим, не в зеттабайте, а в петабайтах плоских текстовых логов. grep использовать будем? Ладно, закроем глаза на то, что выполняться это будет не один год - а искать-то что собрались?

Вы на полном серьезе считаете, что, стоит только произнести "Big Data", и интересующая информация сама себя найдет в этих самых Big Data?

А что вы собираетесь искать - вы знаете? Серьезно?

У вас есть матмодель преступника с профилем преступного поведения по Бертильону? Ах, у вас нет такой матмодели... Да еще и преступники не полные дауны и первую часть статьи они понимают, в отличие от вас...

Итак, что дальше? Вы имеете собранные в одном месте данные всей сетевой движухи граждан. С миллионами грязных секретиков. Вы совершенно не представляете, что с этим делать - ах, нет, что это я. Конечно, вы представляете! Короли шантажа рукоплещут стоя, хакеры всего мира ставят вам памятники в натуральную величину из золота и платины. Кроме того, призрак 1917 года и его родовая травма стучат в ваши сердца. Правда, вот незадача - в те времена не было никакого Интернета и в помине. Сильно это помогло Учредительному Собранию вкупе с Временным Правительством? Опоздали вы с тотальным контролем над Интернетом. На 20 лет опоздали. Тормоза горные. Пока Египет не грянул - никто и не крестился. А ведь вам теперь еще надо все это построить и каким-то образом постараться использовать на практике. Раньше, чем вас хакеры разорвут в клочья. 

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

Четвертая часть Марлезонского балета - немного здравого смысла

Я понимаю, что те, кто затевает все это - здравым смыслом не изуродованы. 

Однако те специалисты, которых за 30к серебренников (реально мелкий прайс) привлекли к этой афере, должны понимать следующее.

БОльшая часть из того, что представляло бы интерес с точки зрения борьбы с преступностью и терроризмом составляют системы, принципиально не поддающиеся технологии MiTM. По определению - они так были разработаны.

Перечислим их:

  1. SSH
  2. Tor и Tor Messenger
  3. P2P и encrypted P2P
  4. Bitmessage
  5. Signal
  6. БОльшая часть мессенджеров. Вы серьезно надеетесь выкрутить руки Цукенбергу, или Паше Дурову? Помните, что он вам показывал? Он не дурак, у него просто фамилия такая.
  7. Некоторые операционные системы. Да-да, я про Тео. Попробуйте на него надавить. С любыми соображениями.
  8. БОльшая часть VPN.
  9. OTR - вы как себе представляете их расшифровку? Грубой силой, на суперкомпьютерах, за астрономическое время? Валяйте.
  10. GnuPG - в девичестве PGP. Надеюсь, вы не считаете, что Фил Циммерман - недоумок или зря сидел в тюрьме?
  11. Любая TLS-based система с технологией pinning и/или OCSP stapling. Зарубите на носу - правильно реализованный TLS не подвержен атаке MiTM ни в каком виде. Вы доиграетесь, что мировое IT-сообщество модифицирует протоколы и RFC до состояния полностью неберущейся технологии. А к этому все и идет. Оборудование на вес будете продавать - позовите. :)
К чему это я? 

К тому, что если вы хотя бы 10% не в состоянии контролировать - то вы ничего не контролируете. А то, что я перечислил - это много больше 10%.

В общем-то понятно, что вышеперечисленные средства, где как раз и находится в теории ваша ЦА, как раз вас и не интересует. Вас интересуют простые маленькие человечки. Которых вы планируете разоружить полностью и бесповоротно и отдать прямо в лапы хакеров. Вы просто ввиду полного отсутствия воображения не в состоянии представить масштабных киберпреступлений. Вы по сторонам-то оглянитесь. Что тащат хакеры? Массивы персональных данных. Что хотите сделать вы? Построить самую заманчивую в мире цель с массивом всех возможных данных. Такие массивы не могли уберечь IT-гиганты - не чета вам, замухрыгам мухосранским. Вы правда надеетесь, что весь мир дураки - одни вы гении?

И немного политики. Вот такие задумки, конечно, ваши проблемы и ночные кошмары частично решают. Однако что китайцу здорово - то остальным смерть. Чебурнетизация, это, конечно, круто. В XXI-то веке. Далеко пойдете. Прямиком в XI век. И не надо кивать на другие страны. Нигде такая блажь не прокатила, исключая Китай - но и ему это боком выходит прямо сейчас. С другой стороны - где Китай и где вы.

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


Часть последняя - юридическая

Хотя в вышеупомянутых странах Конституция не стоит и бумаги, на которой напечатана (собственно говоря, реально действующая конституция на планете всего одна. Та, действие которой подкреплено Второй Поправкой), лицам, принимающим такие решения следует знать, что фактически введение в действие вышеописанных систем является принудительным насаждением презумпции виновности в отношении неопределенного круга лиц. И, как следствие, использует вышеупомянутую конституцию в качестве туалетной бумаги - любой гражданин может быть прослушан без его ведома, без решения суда или ордера, без гарантий государственной охраны полученных подобным образом данных, и эти данные могут быть использованы против гражданина каким угодно образом.

(Не надо кивать на АНБ. Там после поползновений в сторону конституции в отставку вылетают. Давая пинок сами себе - если уж попались на горячем. И это как минимум.)

Это открывает широчайшие возможности для разнообразных злоупотреблений (говоря мягко), под якобы благим предлогом "государственной безопасности" (непобедимый аргумент. Правда, государственная безопасность складывается из безопасности граждан, а тут-то как-раз и неувязочка получается. Взаимоисключающие параграфы.) и, говоря начистоту, рано или поздно приведет принявших и осуществивших такое решение (я уже объяснил, почему именно) непосредственно на скамью подсудимых или вообще поставит перед кирпичной стеной.

Если уж вы такие, с позволения сказать, законы, противоречащие и букве и духу конституции принимаете, в них должно быть четко и честно прописано от и до:


  1. С какой целью и для чего нарушается конституция. Граждане должны знать, что за ними круглосуточно шпионит государство, должны знать причины, по которым это делается. Не надо врать, что это якобы для защиты-обеспечения вашей же безопасности-и бла-бла-бла. Вы еще для нашей безопасности заприте нас в одиночные камеры.
  2. В законе четко должно быть прописано, что именно собирается, как и сколько хранится. Я имею право знать, за чем именно в моих коммуникациях шпионят и как именно. Кроме того, что я также имею право знать, за какие нарушения в этих коммуникациях и как именно я могу быть привлечен к ответственности. 
  3. В законе четко должно быть прописано, как именно собирается информация, сколько хранится, как защищается, кто и на каком основании а также с какой целью может иметь к ней доступ. А также четко прописано, какую ответственность будет нести тот, кто неправомочно получит доступ или использует эту информацию в противоправных целях (и в соответствии с п.1).
  4. Родина должна знать своих героев. Авторы закона должны быть поименно перечислены. Чтобы при неизбежном наступлении последствий знать, кого конкретно привлекать к уголовной ответственности и ВМН. Пока что лишь один всемирно известный закон подобного рода содержит данный пункт. 
Ни с юридический, ни с технической точки зрения подобные решения не проработаны. Помимо того, что они просто являются преступными по своей сути и противоречат элементарной этике. Какими бы благими намерениями это не мотивировалось. Известно, куда ими выстлана дорога.

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

Вы меня поняли?

суббота, 24 сентября 2016 г.

Распознавание Solaris x86/x86_64 компилятором GCC

Нужно отличить для условной компиляции Solaris 10 x86 и x86_64 от остальных систем, включая Solaris SPARC.

Вам это кажется тривиальной задачей? Как бы не так. Попробуйте, если не верите.

После исследования доступных материалов и целой серии экспериментов удалось найти следующее работоспособное решение:

 #if defined (__sun) || (defined (__i386) && defined (__i86pc))  
 // Solaris x86 stuff  
 #endif  
   
   

Вне зависимости от того, какое ядро загружено, Solaris Intel обозначает себя в GCC таким образом. Протестировано на GCC 4.9.2 и 5.2.0 на x86/x86_64 и sun4u.

Замечание. Для Solaris Studio это не работает. Если нужно полностью совместимый код, условие должно быть более сложным.

вторник, 19 июля 2016 г.

Использование Phishtank в ufdbGuard при работе со свободными блоклистами

Как известно, ufdbGuard в своем коммерческом блоклисте содержит базу Phishtank, обновляемую на регулярной основе.

Сам ufdbGuard имеет открытый код и может быть использован с любым доступным в виде плоскотекстовых файлов (plain-text) блоклистами. Однако, в этом случае, о подключении базы Phishtank вам следует позаботиться самостоятельно.

Так как сам автор не может (ввиду конфликта интересов) давать советы о применении некоммерческих блоклистов, сделаю это за него.

Я достаточно давно применяю в своих инсталляциях прокси Shallalist. Хотя авторы коммерческих блоклистов отзываются о нем довольно пренебрежительно, опыт показывает, что он достаточно регулярно обновляется (хотя удаление неактуальных ссылок идет далеко не так активно, как добавление, что ведет к распуханию листа), достаточно эффективен в повседневном бизнес-применении и компилируется в весьма компактные и быстро работающие базы ufdbGuard. 

Сравнительно легко оказалось добавить в него поддержку Phishtank. Phishtank довольно активно поддерживается сообществом, в настоящее время бесплатен к любому применению, и содержит как фишинговые домены, так и URLы. При использовании ufdbGuard версии 1.32 (в настоящее время rc7, еще не выпущен релиз) и домены и URLы компилируются в единые базы в формате gZip, и весьма эффективно работают.

Итак, для начала нужно зарегистрироваться на Phishtank.

Регистрация необходима для получения Developer API key с целью автоматизированной скачки упакованного списка. Затем на этой странице надо выбрать нужный формат и сформировать для себя собственный URL для загрузки с собственным ключом, по этой ссылке ваш скрипт будет автоматически забирать список каждый час (обновления могут происходить достаточно часто, будете обновляться реже - снижается защита, чаще - задолбаете сервер Phishtank и вас в гневе могут побанить).

Так как прямое использование скачанного списка невозможно, потребуется некоторое преобразование полученных данных в вид, пригодный для компиляции в базу ufdbGuard. Напишем для этой задачи скрипт update_phishtank.sh:

 #!/sbin/sh  
   
 # By accepting this notice, you agree to be bound by the following  
 # agreements:  
 #  
 # This script written by Yuri Voinov (C) 2010,2016  
 #  
 # This program is free software; you can redistribute it and/or modify it  
 # under the terms of the GNU General Public License (version 2) as  
 # published by the Free Software Foundation. It is distributed in the  
 # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the  
 # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  
 # PURPOSE. See the GNU General Public License (GPL) for more details.  
 #  
 # You should have received a copy of the GNU General Public License  
 # (GPL) along with this program.  
   
 #  
 # ufdbGuard phishtank update  
 #  
 # Based on shalla_update.sh, v 0.3.1 20080403  
 # and update_blocklist.sh, v 1.1-1.9 by Y.Voinov.  
 #  
 # ident  "@(#)update_phishtank.sh   1.0   28/06/16 YV"  
 #  
   
 #############  
 # Variables #  
 #############  
   
 # Key  
 KEY="94e1ed31858910e63d0c5e344a5672254037421f27ad5b4636f6d1420dea13b4"  
   
 # Modify PATH for SFW directory use  
 PATH=/usr/sfw/bin:$PATH  
   
 # List name  
 LIST_NAME="online-valid.csv.gz"  
   
 # Servers for downloading blacklist  
 SERVER1="http://data.phishtank.com/data/$KEY/$LIST_NAME"  
 SERVER2=""  
   
 SERVER_LIST="$SERVER1 $SERVER2"  
 TEMP_DIR="/tmp"  
 WORK_DIR="$TEMP_DIR/phishtank"  
   
 # Connection timeout for downloading  
 TIMEOUT=30  
   
 # Redirector user name  
 RDR_USER="ufdb"  
 RDR_GROUP="ufdb"  
   
 # Installation base dir  
 BASE="/usr/local"  
 # Redirector dir base  
 BASE2=$BASE"/ufdbguard"  
 # Redirector blacklist base  
 BASE3=$BASE2"/blacklists"  
   
 DIR_CUSTOM2=$BASE3"/phishtank"  
   
 # OS utilities  
 AWK=`which awk`  
 CAT=`which cat`  
 CHMOD=`which chmod`  
 CHOWN=`which chown`  
 CUT=`which cut`  
 ECHO=`which echo`  
 FIND=`which find`  
 GREP=`which grep`  
 GZCAT=`which gzcat`  
 ID=`which id`  
 MKDIR=`which mkdir`  
 PRINTF=`which printf`  
 RM=`which rm`  
 SED=`which sed`  
 SORT=`which sort`  
 TAIL=`which tail`  
 TOUCH=`which touch`  
 UNIQ=`which uniq`  
 WGET=`which wget`  
   
 ###############  
 # Subroutines #  
 ###############  
   
 root_check ()  
 {  
  if [ ! `$ID | $CUT -f1 -d" "` = "uid=0(root)" ]; then  
  $ECHO "ERROR: You must be super-user to run this script."  
  exit 1  
  fi  
 }  
   
 checkuser ()  
 {  
 # Check redirector user  
  username=$1  
  if [ ! -z "`$CAT /etc/passwd | $GREP $username`" ]; then  
  $ECHO "1"  
  else  
  $ECHO "0"  
  fi  
 }  
   
 check_clean ()  
 {  
 # Check that everything is clean before we start  
  if [ -f $WORK_DIR/$LIST_NAME ]; then  
  $PRINTF "Old blacklist file found in ${WORK_DIR}..."  
  $RM $WORK_DIR/$LIST_NAME  
  $ECHO "Deleted."  
  fi  
 }  
   
 set_permission ()  
 {  
 # Setting files permissions  
  if [ "`checkuser $RDR_USER`" = "1" ]; then  
  $PRINTF "\nSetting files permissions..."  
  $CHOWN -R $RDR_USER:$RDR_GROUP $BASE3  
  $CHMOD 755 $BASE3  
  cd $BASE3  
  $FIND . -type f -exec $CHMOD 644 {} \;  
  $FIND . -type d -exec $CHMOD 755 {} \;  
  else  
  $ECHO "ERROR: User $RDR_USER does not exists. Exiting..."  
  exit 5  
  fi  
 }  
   
 download_list ()  
 {  
  # Make working directory  
  if [ ! -d $WORK_DIR ]; then  
  $PRINTF "Make working directory..."  
  $MKDIR -p $WORK_DIR  
  $ECHO "Done."  
  fi  
  # Get list from one server using server list  
  $PRINTF "List downloading..."  
  for S in $SERVER_LIST; do  
  $WGET -T $TIMEOUT -q -O $WORK_DIR/$LIST_NAME $S  
  retcode=`$ECHO $?`  
  case "$retcode" in  
   0)  
   $ECHO "List downloaded successfully."  
   break  
   ;;  
   4)  
   $ECHO "Unable to resolve host address. Exiting..."  
   exit 4  
   ;;  
   *)  
   $ECHO "Error downloading list from `$ECHO $S|$CUT -f1 -d '/'`. Try another server..."  
   continue  
   ;;  
  esac  
  done  
   
  if [ "$retcode" != "0" ]; then  
  $ECHO "Error downloading list from all servers. Exiting..."  
  exit 1  
  fi  
   
  # If destination directory not exists, lets create it  
  if [ ! -d "$DIR_CUSTOM2" ]; then  
  $MKDIR -p $DIR_CUSTOM2  
  fi  
   
  # List transformation  
  $GZCAT $WORK_DIR/$LIST_NAME | $TAIL -n +2 | $AWK -F',' '{ print $2 }'| $SED -e 's/"//' | $SED -e 's/^https\?:\/\///' | $SORT | $UNIQ -u > $DIR_CUSTOM2/domains  
  # Update time for file  
  $TOUCH $DIR_CUSTOM2/domains  
  # Set permissions  
  set_permission  
 }  
   
 clean_up ()  
 {  
 # Clean up file and directories  
  $PRINTF "Clean up downloaded file and directories..."  
  $RM -rf $WORK_DIR  
  $ECHO "Done."  
 }  
   
 ##############  
 # Main block #  
 ##############  
   
 # Root check  
 root_check  
   
 # Check working directory clean  
 check_clean  
   
 # Download list  
 download_list  
   
 # Clean up  
 clean_up  
   
 exit 0  
 #####  

Все необходимые редактируемые параметры находятся в начале скрипта. Предполагается, что выгрузка и преобразование листа производится в директорию /usr/local/ufdbguard/blacklists/phishtank. Вы можете изменить это по своему усмотрению. В переменную KEY впишете ваш собственный полученный ранее API key.

Обратите внимание - данный скрипт скачивает, трансформирует в плоский файл и записывает список Phishtank, устанавливает правильные права для редиректора. Но и только. Ни компиляции листа в формат ufdbGuard, ни перезапуска редиректора он не производит.

Лично я не использую вышеприведенный скрипт непосредственно, а вызываю его из написанного несколько лет назад скрипта, используемого для автоматизированного обновления списка Shallalist update_blocklist.sh:

 #!/sbin/sh  
   
 # By accepting this notice, you agree to be bound by the following  
 # agreements:  
 #  
 # This script written by Yuri Voinov (C) 2010,2016  
 #  
 # This program is free software; you can redistribute it and/or modify it  
 # under the terms of the GNU General Public License (version 2) as  
 # published by the Free Software Foundation. It is distributed in the  
 # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the  
 # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  
 # PURPOSE. See the GNU General Public License (GPL) for more details.  
 #  
 # You should have received a copy of the GNU General Public License  
 # (GPL) along with this program.  
   
 #  
 # ufdbGuard blocklist update  
 #  
 # Based on shalla_update.sh, v 0.3.1 20080403  
 # and update_blocklist.sh, v 1.1-1.9 by Y.Voinov.  
 #  
 # ident  "@(#)update_blocklist.sh   2.0   28/06/16 YV"  
 #  
   
 ## NOTE: You can select redirector compilation categories by edit CATEGORIES variable for compilation.  
   
 #############  
 # Variables #  
 #############  
   
 # ------------- Blacklist options ------------------  
 CATEGORIES="adv anonvpn costtraps dating drugs fortunetelling gamble hacking hobby/games-misc hobby/games-online models movies music porn redirector sex/lingerie socialnet spyware tracker updatesites urlshortener warez webradio webtv"  
 # ------------- Blacklist options ------------------  
   
 # Modify PATH for SFW directory use  
 PATH=/usr/sfw/bin:$PATH  
   
 # List name  
 LIST_NAME="shallalist.tar.gz"  
   
 # Servers for downloading blacklist  
 SERVER1="http://www.shallalist.de/Downloads/$LIST_NAME"  
 SERVER2=""  
   
 SERVER_LIST="$SERVER1 $SERVER2"  
 TEMP_DIR="/tmp"  
 WORK_DIR="$TEMP_DIR/shalla"  
   
 # Connection timeout for downloading  
 TIMEOUT=30  
   
 # SMF name  
 SMF_NAME="svc:/network/ufdbguard:default"  
   
 # Redirector user name  
 RDR_USER="ufdb"  
 RDR_GROUP="ufdb"  
   
 # Installation base dir  
 BASE="/usr/local"  
 # Redirector dir base  
 BASE2=$BASE"/ufdbguard"  
 # Redirector blacklist base  
 BASE3=$BASE2"/blacklists"  
   
 # Redirector convert tool location  
 RDR_GUARD_DB_TOOL=$BASE2"/bin/ufdbConvertDB"  
 # Redirector daemon  
 RDR_BIN_FILE="ufdbguardd"  
   
 DIR_ALLOW=$BASE3"/alwaysallow"  
 DIR_DENY=$BASE3"/alwaysdeny"  
 DIR_CUSTOM=$BASE3"/yoyo"  
 DIR_CUSTOM2=$BASE3"/phishtank"  
   
 # OS utilities  
 AWK=`which awk`  
 BASENAME=`which basename`  
 CAT=`which cat`  
 CD=`which cd`  
 CHOWN=`which chown`  
 CHMOD=`which chmod`  
 CP=`which cp`  
 CUT=`which cut`  
 ECHO=`which echo`  
 EXPRT=`which expr`  
 FIND=`which find`  
 GETOPT=`which getopt`  
 GREP=`which grep`  
 GZCAT=`which gzcat`  
 ID=`which id`  
 KILL=`which kill`  
 MKDIR=`which mkdir`  
 NEWTASK=`which newtask`  
 PRINTF=`which printf`  
 PS=`which ps`  
 RM=`which rm`  
 SVCADM=`which svcadm`  
 TOUCH=`which touch`  
 GTAR=`which gtar`  
 UNAME=`which uname`  
 WGET=`which wget`  
   
 ###############  
 # Subroutines #  
 ###############  
   
 usage_note ()  
 {  
 # Script usage note  
  $ECHO "Usage: `$BASENAME $0` [-h][-f][-a]"  
  $ECHO  
  $ECHO "No args - default mode. Full update and recompilation."  
  $ECHO "a - Ads & phishtank & always lists recompilation (whenever updated/exists or not)"  
  $ECHO "f - full update and recompilation."  
  $ECHO "h - this screen."  
  $ECHO "Beware: Categories with .notused file in dir will never be compiled."  
  exit 0  
 }  
   
 root_check ()  
 {  
  if [ ! `$ID | $CUT -f1 -d" "` = "uid=0(root)" ]; then  
  $ECHO "ERROR: You must be super-user to run this script."  
  exit 1  
  fi  
 }  
   
 checkuser ()  
 {  
 # Check redirector user  
  username=$1  
  if [ ! -z "`$CAT /etc/passwd | $GREP $username`" ]; then  
  $ECHO "1"  
  else  
  $ECHO "0"  
  fi  
 }  
   
 check_clean ()  
 {  
 # Check that everything is clean before we start  
  if [ -f $WORK_DIR/$LIST_NAME -o -f $WORK_DIR/shallalist.tar ]; then  
  $PRINTF "Old blacklist file found in ${WORK_DIR}..."  
  $RM $WORK_DIR/shallalist.*  
  $ECHO "Deleted."  
  fi  
   
  if [ -d $WORK_DIR/BL ]; then  
  $PRINTF "Old blacklist directory found in ${WORK_DIR}..."  
  $RM -rf $WORK_DIR/BL  
  $ECHO "Deleted."  
  fi  
 }  
   
 download_ads ()  
 {  
  # If Yoyo adblock script exists, run it first  
  [ -x $BASE/bin/create_filter_ad_servers.sh ] && $ECHO "Ads download script exists." && $BASE/bin/create_filter_ad_servers.sh  
 }  
   
 download_phishtank ()  
 {  
  # If Phishtank script exists, run it first  
  [ -x $BASE/bin/update_phishtank.sh ] && $ECHO "Phishtank download script exists." && $BASE/bin/update_phishtank.sh  
 }  
   
 download_list ()  
 {  
  # Make working directory  
  if [ ! -d $WORK_DIR ]; then  
  $PRINTF "Make working directory..."  
  $MKDIR -p $WORK_DIR  
  $ECHO "Done."  
  fi  
  # Get list from one server using server list  
  $PRINTF "Shalla list downloading..."  
  for S in $SERVER_LIST; do  
  $WGET -T $TIMEOUT -q -O $WORK_DIR/$LIST_NAME $S  
  retcode=`$ECHO $?`  
  case "$retcode" in  
   0)  
   $ECHO "Shalla list downloaded successfully."  
   break  
   ;;  
   4)  
   $ECHO "Unable to resolve host address. Exiting..."  
   exit 4  
   ;;  
   *)  
   $ECHO "Error downloading list from `$ECHO $S|$CUT -f1 -d '/'`. Try another server..."  
   continue  
   ;;  
  esac  
  done  
   
  if [ "$retcode" != "0" ]; then  
  $ECHO "Error downloading list from all servers. Exiting..."  
  exit 1  
  fi  
 }  
   
 compile_always ()  
 {  
 # Compile alwaysallow & alwaysdeny & custom databases if they exists  
  $PRINTF "Compile always and custom databases..."  
  if [ -d $DIR_ALLOW ]; then  
  $PRINTF "allow exists..."  
  $RDR_GUARD_DB_TOOL -d $DIR_ALLOW >/dev/null 2>&1  
  $PRINTF "Done..."  
  fi  
   
  if [ -d $DIR_DENY ]; then  
  $PRINTF "deny exists..."  
  $RDR_GUARD_DB_TOOL -d $DIR_DENY >/dev/null 2>&1  
  $PRINTF "Done..."  
  fi  
   
  if [ -d $DIR_CUSTOM ]; then  
  $PRINTF "custom exists..."  
  $RDR_GUARD_DB_TOOL -d $DIR_CUSTOM >/dev/null 2>&1  
  $PRINTF "Done..."  
  fi  
   
  if [ -d $DIR_CUSTOM2 ]; then  
  $PRINTF "custom 2 exists..."  
  $RDR_GUARD_DB_TOOL -d $DIR_CUSTOM2 >/dev/null 2>&1  
  $PRINTF "Done..."  
  fi  
 }  
   
 set_permission ()  
 {  
 # Setting files permissions  
  if [ "`checkuser $RDR_USER`" = "1" ]; then   
  $PRINTF "\nSetting files permissions..."  
  $CHOWN -R $RDR_USER:$RDR_GROUP $BASE3  
  $CHMOD 755 $BASE3  
  cd $BASE3  
  $FIND . -type f -exec $CHMOD 644 {} \;  
  $FIND . -type d -exec $CHMOD 755 {} \;  
  else  
  $ECHO "ERROR: User $RDR_USER does not exists. Exiting..."  
  exit 5  
  fi  
 }  
   
 fast_list_compilation ()  
 {  
  # First download ads if script exists  
  download_ads  
  # Then download phishtank if script exists  
  download_phishtank  
  # Ads & phishtank & always lists compilation if they exists  
  $PRINTF "Ads & phishtank & always lists compilation only..."  
  compile_always  
  # Set files permissions  
  set_permission  
  $ECHO "Done."  
 }  
   
 full_list_compilation ()  
 {  
  # First download ads if script exists  
  download_ads  
  # Then download phishtank if script exists  
  download_phishtank  
  # Then download blocklist  
  download_list  
  # Unpack list  
  $PRINTF "List unpacking..."  
  $GZCAT $WORK_DIR/$LIST_NAME | $GTAR -x -C $WORK_DIR  
  $CP -rp $WORK_DIR/BL $BASE3  
  $ECHO "Done."  
  # Call recompilation for categories defined in list  
  $PRINTF "Databases compiling..."  
  for cat in $CATEGORIES  
  do  
  # Update date and time to current to be compiled  
  $TOUCH $BASE3/BL/${cat}/domains >/dev/null 2>&1  
  $TOUCH $BASE3/BL/${cat}/urls >/dev/null 2>&1  
  $RDR_GUARD_DB_TOOL -d $BASE3/BL/${cat} >/dev/null 2>&1  
  done  
  $ECHO "Done."  
  compile_always  
  # Set files permissions  
  set_permission  
  $ECHO "Done."  
 }  
   
 reconfiguration ()  
 {  
  os=`$UNAME`  
  if [ "$os" = "SunOS" ]; then  
  $PRINTF "Redirector daemon restart..."  
  # Redirector restart on Solaris-based boxes  
  $SVCADM -v restart $SMF_NAME  
  else  
  $PRINTF "Redirector daemon reconfiguration..."  
  # Redirector reconfiguration  
  program=$1  
  pid=`$PS -ef|$GREP $program|$GREP -v grep|$AWK '{ print $2 }'`  
  $KILL -HUP $pid  
  fi;  
  $ECHO "Done."  
 }  
   
 clean_up ()  
 {  
 # Clean up file and directories  
  $PRINTF "Clean up downloaded file and directories..."  
  $RM -rf $WORK_DIR  
  $ECHO "Done."  
 }  
   
 ##############  
 # Main block #  
 ##############  
   
 # Root check  
 root_check  
   
 # Check clean working dir  
 check_clean  
   
 # BL download, unpack and compilation  
 # Check command-line arguments  
 if [ "x$*" = "x" ]; then  
 # If arguments list empty, make compilation by default  
  full_list_compilation  
 else  
  arg_list=$*  
  # Parse command line  
  set -- `$GETOPT aAfFhH: $arg_list` || {  
  usage_note 1>&2  
  }  
   
  # Read arguments  
  for i in $arg_list  
  do  
   case $i in  
   -a | -A) fast_list_compilation;;  
   -f | -F) full_list_compilation;;  
   -h | -H | \?) usage_note;;  
   esac  
   break  
  done  
   
  # Remove trailing --  
  #shift `$EXPR $OPTIND - 1`  
 fi  
   
 # Redirector reconfiguration  
 reconfiguration $RDR_BIN_FILE  
   
 # Finally cleanup all downloaded files  
 clean_up  
   
 exit 0  
 ####  

Замечание. Обратите внимание вот на что. Скрипт update_phishtank.sh - ОС-неспецифичный, в отличие от скрипта update_blocklist.sh, который содержит соляризмы, в частности, используется SMF для рестарта сервиса ufdbGuard. Вы можете переписать подпрограмму рестарта сервиса под вашу собственную ОС. Так как я в основном применяю Solaris, я, разумеется, не буду делать этого за вас. :)

После первоначальной скачки и компиляции списка, вы можете его добавить в конфигурацию ufdbGuard.conf:

Сначала создадим категорию:

 category phishtank {  
     domainlist "phishtank/domains"  
      redirect "http://cthulhu.localdomain:8080/cgi-bin/URLblocked.cgi?clientgroup=%s&clientaddr=%a&category=%t&url=%u"  
 }  
   

Понятно, да, что ссылка для редиректа будет ваша собственная и, вообще говоря, редиректы уже должны быть настроены? :) Не спрашивайте меня, как это делать - курите маны.

Теперь можно добавить категорию в ACL редиректора:

 default {  
    ### NOTE: depending on the other ACLs, "pass none" may be more appropriate to use  
    ### EDIT THE NEXT LINE FOR LOCAL CONFIGURATION:  
           pass !security !alwaysdeny updatesites !adv !phishtank !drugs !gamble !fortunetelling  
                !porn !sex !dating !spyware !hacking  
                !anonvpn !redirector !costtraps !social  
                !games !games2 !tracker !movies !music  
                !models !urlshortener !webtv !webradio !warez  
                any  
    #!adv2  
    # pass none  
     redirect "http://cthulhu.localdomain:8080/cgi-bin/URLblocked.cgi?clientgroup=%s&clientaddr=%a&category=%t&url=%u"  
   }  
 }  
   

и перезапустить его. That's all, folks! Теперь ваши хомячки в некоторой (относительной!) безопасности.

DISCLAMER. Я в курсе, что не существует стопроцентной защиты. Я в курсе, что в борьбе щита и меча обычно побеждает меч. Я в курсе, что жульё плодится быстрее, чем вымирают лохи. Однако все это позволяет еще немного уменьшить площадь поражения ваших неразумных подопечных - да и вас вместе с ними. Безопасности вам и вашим пользователям!

пятница, 17 июня 2016 г.

Сон разума рождает чудовищ

С интересом слежу, куда рассуждения заведут вот этого господина. Прочитайте все статьи, прежде, чем читать дальше.

В дискуссии с философами вступать смысла не вижу ("Люблю философов - не люблю философии" Т.Афинская), однако некоторые забавные мысли заводятся.

Мысль первая. Поздно пить боржоми, когда почки отвалились.

Философствовать и очковать надо было раньше. Азимов будет в гробу вертеться, как пропеллер Ту-95. Вместе с Лемом. Обезьяна - она ведь как, не попробует - естественный отбор ее не прихлопнет. С учетом всеобщей тенденции к росту биомассы при константном интеллекте туда и дорога - в батарейки сестер Вачовски. Ей-богу, не жалко. Помашем Илону платочком, при отбытии оного в его последний путь на Марс.

Мысль вторая. Основанная на цитатках от автора: "А мы не знаем и не можем знать, что там внутри. Ну и контролировать".

Мужик. Ты точно так же не знаешь, что в башке твоих детей. И точно так же не способен к какому бы то ни было реальному контролю. Все человечество, несмотря на титанические усилия и бесчеловечные опыты Павлова, Менгеле и иже с ними - не знает. Может вырасти Сальватор Дали, а может - Адольф Гитлер. Такие дела.

Мысль третья. Когда не хватает тяму понять, что состряпывается явно инструмент судного дня - тобой, любимым - самое правильное - поехать на эвтаназию. Но что-то я там Оппенгеймеров в очередях, как в мавзолей Вождя мирового пролетариата, не вижу. Почему в начале 20 столетия все физики мира не совершили массовый суицид в ужасе? А раз так - какого дьявола ты кликушествуешь? Делаешь - не ты. Они делают по принципу - because we can (c) Nickelodeon. К черту этику. Сперва сделаем - а война план покажет. Ну, заберет естественный отбор. Так "после нас хоть потоп". Что изменится от твоих откровений? Даже если все человечество выползет на Монстрацию Луддитов, уничтожая компьютеры, сети и интернет (ха, кто ж ему позволит-то такую кормушку завалить, да и, ко всему, как стадо киборгов выживет без своих говноклассничков, этих отхожих мест якобы "интеллектов"?) - что изменится? Санитаров леса - сиречь хакеров - то самое озабоченное сексуально человечество стремительно винтит и закатывает в места не столь отдаленные.

Мужик - ты и вправду веришь в здравый смысл коллективного бессознательного?

А, кстати - Пентагон с их DARPA и буквально без пяти минут робокопами при пулеметах - тоже уничтожить прикажешь? Одни уже попытались. Типа. 11/11. Какая-то хилая попытка оказалась.

Участь пророков - сдохнуть на кресте. Участь гениев - в лучшем случае Матрицы (хотя скорее следует ожидать Скайнета, нежели Нео) - такая же. Философы составят компанию  - много болтали. Хочешь ты этого или нет. Вне зависимости от того, как долго, много, качественно и обоснованно ты будешь болтать языком о грядущих катастрофах.

Да, на эвтаназию гениев не заманишь. Сами не пойдут. Вести под дулами автоматов - опасно попахивает Адольфом Шикльгрубером. А альтернативы-то нет. Либо тотальное возвращение естественного отбора по Дарвину - либо эвтаназия создателей всего потенциально опасного. Третьего не дано. Точнее, оно дано - но только Илону. И то не прямо сейчас.

Так что хорош болтать. Расслабь булки - и попытайся получить удовольствие. "Что исправить не в силах - терпи до могилы". Что вырастет - то вырастет. Как из твоих собственных детей.

понедельник, 23 мая 2016 г.

Oracle Text: Высокоскоростной парсер поисковых запросов, часть III

Документация пакета CTX_API




Продолжение следует.



Oracle Text: Высокоскоростной парсер поисковых запросов, часть II

Введение

Окей, в предыдущей части вкратце была рассмотрена логика высокоскоростного парсера поисковых запросов в Oracle Text.

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

Это будет всего лишь дословный поиск по ключевым словам, что в большинстве случаев довольно-таки далеко от контекстного поиска.

Более того, это совсем не контекстный поиск. Как бы его не преподносили маркетологи компаний, использующих данный функционал. В нем нет ни расширения запросов по смыслу, ни поиска по темам (хотя это и возможно реализовать с использованием функций базисного функционала Oracle Text, в сравнении с поиском, например, Гугл, это всего лишь жалкое подобие левой руки).

Дело, собственно говоря, вот в чем. 

В Oracle Text есть единственная функция контекстного поиска. И это about()

Однако, проблема заключается в следующем.

Функция about() в отсутствие загруженных тезаурусов на соответствующих языках работает как простой поиск по ключевым словам. Причем:

  1. Тезаурусы по умолчанию не загружены
  2. В состав Oracle RDBMS включены считанные единицы тезаурусов на некоторых европейских языках
  3. Включенные в состав Oracle тезаурусы написаны индийскими студентами за еду и практически непригодны ввиду своего содержания для реального поиска
  4. Тезаурус русского языка отсутствует
  5. Тезаурусы Oracle должны иметь иерархическую структуру, что делает крайне сложным их разработку, сопровождение и применение в рамках сколько-нибудь сложного поискового функционала
Кроме того, в отсутствие тезаурусов принципиально сложно реализовать что-то сколько-нибудь похожее на функционал Гугла "Возможно, вы имели в виду....".

Данный парсер является попыткой решить часть этих задач:
  1. Попытаться все-таки добиться правильной работоспособности функции abount()
  2. Создать некий фреймворк для реализации гуглоподобного функционала "Возможно, вы имели в виду....", хотя и в несколько ином виде
  3. Создать некую обертку вокруг функционала Oracle Text по поддержке тезаурусов
  4. Разумеется, сохранить функциональность тупого дубового поиска "в лоб" по ключевым словам
  5. Создать некое подобие поискового языка запросов а-ля Гугл

Пакет ctx_api: парсер и функционал тезауруса

Итак, вот полный код пакета.

Спецификация ctx_api.sql:
 -------------------------------------------------------------------------------  
 -- PROJECT_NAME: CTX_API                           --  
 -- RELEASE_VERSION: 1.0.0.5                         --  
 -- RELEASE_STATUS: Release                          --  
 --                                      --  
 -- REQUIRED_ORACLE_VERSION: 10.1.0.x                     --  
 -- MINIMUM_ORACLE_VERSION: 9.2.0.3                      --  
 -- MAXIMUM_ORACLE_VERSION: 11.x.x.x                     --  
 -- PLATFORM_IDENTIFICATION: Generic                     --  
 --                                      --  
 -- IDENTIFICATION: ctx_api.sql                        --  
 -- DESCRIPTION: CTX_API package. Contains ConText Search and thesaurus API. --  
 -- ------------------------------------------------------------------------- --  
 -- Package table of contents, syntax and components descriptions:      --  
 -- ------------------------------------------------------------------------- --  
 -- function version return varchar2 deterministic;              --  
 --                                      --  
 -- Function returns hardcoded API version for application alternative calls. --  
 -- No arguments.                               --  
 -- ------------------------------------------------------------------------- --  
 -- function phrase_exists (p_phrase in varchar2,               --  
 --             p_thes_name in varchar2              --  
 --             default 'default') return boolean         --  
 --             deterministic;                  --   
 --                                      --  
 -- Function checks phrase exists in specified thesaurus.           --  
 --                                      --   
 -- Arguments:                                --  
 -- p_phrase - specified term. Can be phrase and can contain qualifier.    --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- function phrase_relation_exists (p_phrase in varchar2,          --  
 --                 p_relation in varchar2          --  
 --                 default 'bt,btp,nt,ntp,rt,syn',     --  
 --                 p_thes_name in varchar2         --  
 --                 default 'default') return boolean    --  
 --                 deterministic;              --  
 --                                      --  
 -- Function checks relations in thesaurus for specified term.        --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - specified term. Can be phrase and can contain qualifier.    --  
 -- p_relation - relations list. Can contain one or more standard relation  --  
 --       keywords with comma-separated list.             --  
 --       Examples: 'bt,ntg','syn','rt','bt,btp,ntp' etc.       --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- function search_expansion_level (p_phrase in varchar2,          --  
 --                 p_thes_name in varchar2         --  
 --                 default 'default') return number     --  
 --                 deterministic;              --  
 --                                      --  
 -- Function returns BT/NT relations expansion level, which contains more   --  
 -- than c_nt_terms NT's for phrase subtree.                 --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - specified term. Can be phrase and can contain qualifier.    --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- Known issues: Function returns 0 if phrase is Top Term or absent in thes. --  
 -- ------------------------------------------------------------------------- --  
 -- function search_expansion_term (p_phrase in varchar2,           --  
 --                 p_thes_name in varchar2          --  
 --                 default 'default') return varchar2    --  
 --                 deterministic;              --  
 --                                      --  
 -- Function returns BT subcategory for given phrase subtree, for which BT  --  
 -- contains more than c_nt_terms NT's.                    --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - specified term. Can be phrase and can contain qualifier.    --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 --                                      --  
 -- Known issues: Function returns 0 if phrase is Top Term or absent in thes. --  
 -- ------------------------------------------------------------------------- --  
 -- function has_homographs (p_phrase in varchar2,              --   
 --             p_thes_name in varchar2             --  
 --             default 'default') return boolean        --  
 --             deterministic;                  --  
 --                                      --  
 -- Function checks therm for homographs. If homographs exists, returns true. --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - term for checking.                       --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_qualifiers ( p_qualifiers out ctx_api.term_tab,       --  
 --              p_phrase in varchar2,             --  
 --              p_thes_name in varchar2 default 'default');  --  
 --                                      --  
 -- Procedure returns term qualifiers if they exists. If term not exists in  --  
 -- thesaurus, exception ORA-20151 raised.                  --  
 --                                      --  
 -- Arguments:                                --  
 -- p_qualifiers - return structure (table of varchar2) by ctx_api.term_tab. --  
 -- p_phrase - term for checking.                       --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 --                                      --  
 -- Known issues: If term is only one (has no homograps), but defined with  --  
 -- qualifier, procedure returns this qualifier anyway.            --  
 -- ------------------------------------------------------------------------- --  
 -- function get_note (p_phrase in varchar2,                 --  
 --          p_thes_name in varchar2                --  
 --          default 'default') return varchar2           --  
 --          deterministic;                     --  
 --                                      --  
 -- Get scope note (SN) for phrase if it exists. Otherwise function returns  --  
 -- empty string.                               --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - specified term. Can be phrase and can contain qualifier.    --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- function get_bt (p_phrase in varchar2,                  --  
 --         p_level in number default 1,               --  
 --         p_thes_name in varchar2 default 'default')        --  
 --         return varchar2 deterministic;              --   
 --                                      --  
 -- Function returns single BT for term.                   --  
 -- If term not exists in thesaurus, then returns themself.          --  
 -- If term has homographs, but no qualifiers specified, returns exception  --  
 -- ORA-20152.                                --   
 -- If term has homographs, and qualifier specified, returns BT term with   --  
 -- specified level (p_level).                        --  
 --                                      --  
 -- Arguments:                                --  
 -- p_phrase - term or phrase for BT.                     --  
 -- p_level - expansion level for BT.                     --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- Known issues: If term has only BTP hierarchical relation, it threats as  --  
 --        BT relation.                        --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_bt (p_bt out ctx_api.term_tab,               --  
 --          p_phrase in varchar2,                 --  
 --          p_level in number default 1,              --  
 --          p_thes_name in varchar2 default 'default');      --  
 --                                      --  
 -- Procedure returns all BT's for term (BT subtree).             --  
 -- If term not exists in thesaurus, then returns themself.          --  
 -- If term has homographs, but no qualifiers specified, returns all BT's   --  
 -- subtrees, one by one, each starting with given term with qualifier.    --  
 -- If term has homographs and qualifier specified, procedure returns only it --  
 -- BT's subtree.                               --  
 -- Note: Output table contains BT's sorted by level in descending order.   --  
 -- Example:                                 --  
 --    cat    - lowest level                      --  
 --    animals  - level up                        --  
 --    zoology  - level up                        --  
 --    science  - level up                        --  
 --                                      --  
 -- Arguments:                                --  
 -- p_bt - return structure (table of varchar2) by ctx_api.term_tab.     --  
 -- p_phrase - term or phrase for BT.                     --  
 -- p_level - expansion level for BT. Restricts expansion level.       --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- Known issues: If term has only BTP hierarchical relation, it threats as  --  
 --        BT relation.                        --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_nt (p_nt out ctx_api.term_tab,               --  
 --          p_phrase in varchar2,                  --  
 --          p_level in number default 1,              --  
 --          p_thes_name in varchar2 default 'default');       --  
 --                                      --  
 -- Procedure returns NT's for term.                     --  
 -- If term not exists in thesaurus, returns exception ORA-20151.       --  
 -- If term has homographs, but no qualifiers specified, returns exception  --  
 -- ORA-20152.                                --  
 -- If term has homographs, and qualifier specified, returns NT's for term  --  
 -- with specified level (p_level).                      --  
 -- If term is lowest level (has no NT's), then return themself.       --  
 --                                      --  
 -- Arguments:                                --  
 -- p_nt - return structure (table of varchar2) by ctx_api.term_tab.     --  
 -- p_phrase - term or phrase for BT.                     --  
 -- p_level - expansion level for BT.                     --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_ntp (p_ntp out ctx_api.term_tab,              --  
 --          p_phrase in varchar2,                 --  
 --          p_level in number default 1,              --  
 --          p_thes_name in varchar2 default 'default');      --  
 --                                      --  
 -- Procedure returns NTP's for term.                     --  
 -- If term not exists in thesaurus, returns exception ORA-20151.       --  
 -- If term has homographs, but no qualifiers specified, returns exception.  --  
 -- If term has homographs, and qualifier specified, returns NTP's for term  --  
 -- with specified level (p_level).                      --  
 -- If term is lowest level (has no NTP's), then return themself.       --  
 --                                      --  
 -- Arguments:                                --  
 -- p_ntp - return structure (table of varchar2) by ctx_api.term_tab.     --  
 -- p_phrase - term or phrase for BT.                     --  
 -- p_level - expansion level for BT.                     --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_rt (p_rt out ctx_api.term_tab,               --  
 --         p_phrase in varchar2,                  --  
 --         p_thes_name in varchar2 default 'default');       --  
 --                                      --  
 -- Procedure returns RT's for term.                     --  
 -- If term not exists in thesaurus, returns exception ORA-20151.       --  
 -- If term has homographs, but no qualifiers specified, returns exception  --  
 -- ORA-20152.                                --  
 -- If term has homographs, and qualifier specified, returns RT's for term.  --  
 -- If term has no RT's, then return themself.                --  
 --                                      --  
 -- Arguments:                                --  
 -- p_rt - return structure (table of varchar2) by ctx_api.term_tab.     --  
 -- p_phrase - term or phrase for RT.                     --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- procedure get_syn (p_syn out ctx_api.term_tab,              --  
 --          p_phrase in varchar2,                  --  
 --          p_thes_name in varchar2 default 'default');       --  
 --                                      --  
 -- Procedure returns SYN's for term.                     --  
 -- If term not exists in thesaurus, returns exception ORA-20151.       --  
 -- If term has homographs, but no qualifiers specified, returns exception.  --  
 -- If term has homographs, and qualifier specified, returns SYN's for term. --  
 -- If term has no SYN's, then return themself.                --  
 --                                      --  
 -- Arguments:                                --  
 -- p_syn - return structure (table of varchar2) by ctx_api.term_tab.     --  
 -- p_phrase - term or phrase for SYN.                    --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- ------------------------------------------------------------------------- --  
 -- function search_string_parser (p_search_str in varchar2,         --  
 --                p_query_mode in varchar2 default 'keyword',--  
 --                p_logical_op in varchar2 default 'and',  --  
 --                p_query_opt in varchar2          --  
 --                default ctx_api.c_query_op_about,     --  
 --                p_expansion_level in number default 1,   --  
 --                p_thes_name in varchar2 default 'default', --  
 --                p_refine_on in number           --  
 --                default ctx_api.c_refine_off,       --  
 --                p_exp_detail_on in number         --  
 --                default ctx_api.c_exp_detail_off)     --  
 --                return varchar2 deterministic;       --  
 --                                      --  
 -- Function is universal parser, supports multiply thesauruses and main   --  
 -- ISO-2788 and ANSI Z39.19 relations. Also supports all logical operands  --  
 -- and composed phrases and qualifiers.                   --  
 --                                      --  
 -- Arguments:                                --  
 -- p_search_string - incoming search string in varchar2 single-byte charset, --  
 --          string length must not be greater than 4000 chars.   --  
 --          When parameter value is null, function returns null.  --  
 -- p_query_mode - parameter controls parsing logic for two different modes: --  
 --        - KEYWORD search (thesaurus functions not uses);      --  
 --        - CONCEPT search (thesaurus functions uses);        --  
 --        The parameters values are: 'keyword' or 'concept'.     --  
 --        Default value is 'keyword' (no thesaurus loaded or cannot --  
 --        identify query mode from parameter).            --  
 -- p_logical_op - controls how tokens will be joined in return string.    --  
 --        Having two values: and, or. Anyway, this is logical    --  
 --        operand for search tokens. Default value is AND.      --  
 -- p_query_opt - controls using thesaurus function in CONCEPT mode. Having  --  
 --        five possible values:                    --  
 --        about, nt, bt, rt or syn. Default value is 'about'.     --  
 --        In keyword mode will be ommitted.              --   
 -- p_expansion_level - expansion hierarchical functions (relations) level.  --  
 --           Will be used only with nt and bt relations in concept --  
 --           mode. In keyword mode and with syn, rt and about will --  
 --           be ignored. If specified default value or 1,then will --  
 --           be ommitted and default level 1 will be used.     --  
 -- p_thes_name - thesaurus name in concept mode. Default value is 'default'. --  
 --        In keyword mode, with about and if value is 'default', it  --  
 --        will be ommitted. Value can be <= 30 characters.      --   
 -- p_refine_on - Refine search flag.                     --   
 --      If 0 (default), query will not be refined.           --  
 --      If 1, context refiner subfunction is ON (only in CONCEPT query --  
 --      mode, when using thesaurus) and any words not satisfied main  --  
 --      query context will be dropped from result string.       --  
 --      See documentation to learn how context refiner function works. --  
 -- p_exp_detail_on - Expand NT/BT queries category level. If enabled,    --  
 --          expansion level is set to subtree category, in which  --  
 --          NT's more than c_nt_terms constant.           --  
 -- NOTE: If this flag enabled, p_expansion_level WILL BE IGNORED.      --  
 -- ------------------------------------------------------------------------- --  
 -- Function parses search string using this RULES:              --  
 -- 1. All punctuation removes from string.                  --  
 -- 2. All garbage symbols (which can generate problems with ConText) removes.--  
 -- 3. String disassembles to the tokens, analyzes and assembles back.    --  
 -- 4. Before assembling, logical operators and thesaurus functions adds to  --  
 --  tokens.                                --  
 -- 5. When analyzes, some syntax uses as controls:              --  
 --  - Words wrapped into "" threats as single token;            --    
 --  - Words having thesaurus qualifier (i.e. cat (animals) ) threats as  --  
 --   single token, qualifier remains;                   --  
 --  - Words concatenated with qualifier (i.e. cat(animals) ) also threats --  
 --   as single token, qualifier remains;                 --  
 --  - Empty brackets removes from string (threats as no token);      --  
 --  - When "" not even in search string, last " will be ignored.      --  
 -- 6. All 'unsafe' constructions wrappes into escape symbols ({}).      --  
 -- 7. ConText reserved words removes from search string, or escapes depends --  
 --  p_query_mode value.                          --   
 -- 8. If metasymbols '%' or '_' found in search string, they pass through in --  
 --  output string in KEYWORD mode, and will be removed in CONCEPT mode.  --  
 -- ------------------------------------------------------------------------- --                                     --  
 -- Usage example:                              --  
 -- SQL> declare                               --  
 -- 2  v_str varchar2(50) := 'кот пес котопес (животные)';         --  
 -- 3  v_out varchar2(50);                         --  
 -- 4 begin                                 --  
 -- 5  v_out := ctx_api.search_string_parser(v_str,'concept','and','nt');  --  
 -- 6  dbms_output.put_line(v_out);                     --  
 -- 7 end;                                 --  
 -- 8 /                                   --  
 -- nt({кот}) and nt({пес}) and nt({котопес})                 --  
 --                                      --  
 -- PL/SQL procedure successfully completed.                 --  
 -- ------------------------------------------------------------------------- --  
 -- function term_counter (p_thes_name in varchar2 default 'default')     --  
 --            return number;                   --  
 --                                      --  
 -- Function counts distinct terms in specified thesaurus.          --  
 --                                      --  
 -- Arguments:                                --  
 -- p_thes_name - thesaurus name. Default value is 'default'.         --  
 -- If thesauri not found, returns exception ORA-20150.            --  
 -- ------------------------------------------------------------------------- --  
 -- procedure thes_loaded (p_ths_list out ctx_api.thes_tab);         --  
 --                                      --  
 -- Procedure returns loaded thesauri list. If no thesauri loaded, returns  --  
 -- exception ORA-20154.                           --  
 --                                      --  
 -- Arguments:                                --  
 -- p_ths_list has type ctx_api.thes_tab which is thesauri name list of    --  
 -- varchar2(30).                               --  
 -- ------------------------------------------------------------------------- --  
 -- Known issues:                               --  
 -- 1. p_thes_name cannot contain "_" symbols,                --   
 --  it will be removed from result string!                 --  
 -- 2. If p_exp_detail_on is enabled,                     --  
 --  p_expansion_level value will be ignored.                --  
 -- 3. If p_query_opt in ('about','syn','rt') or               --  
 --  p_query_mode = 'keyword',                       --  
 --  p_exp_detail_on will be ignored.                    --  
 -- ------------------------------------------------------------------------- --  
 --                                      --  
 -- INTERNAL_FILE_VERSION: 0.0.1.3                      --  
 --                                      --  
 -- COPYRIGHT: Yuri Voinov (C) 2004, 2016                   --  
 --                                      --  
 -- MODIFICATIONS:                              --  
 -- 23.05.2016 -Update copyright.                       --  
 -- 29.03.2008 -Functions phrase_exists and get_note added.          --  
 -- 10.08.2007 -Thesaurus content API added.                 --  
 -- 28.12.2006 -Added overloaded proc get_bt for returning subtrees of BT's. --  
 -- 19.12.2006 -Fix major bug in has_homographs function. Add get_rt, get_syn --  
 --       functions.                          --  
 -- 09.12.2006 -Rename all package constants.                 --  
 -- 03.12.2006 -Type qual_tab and get_qualifiers procedure added. version and --  
 --       has_homographs functions added.                --  
 -- 18.11.2006 -Query options constants added. Expansion level definer const --  
 --       and related functions added. Relation exists function added. --  
 -- 18.06.2006 -Refiner constants added.                   --  
 -- 12.06.2006 -Refiner function added. Free memory call optimized. Exception --  
 --       TEXT_ERROR added (ORA-20150), also when no thesaurus.     --   
 -- 01.03.2006 -p_expansion_level and p_thes_name parameters added.      --  
 -- 18.02.2006 -Initial code written.                     --  
 -------------------------------------------------------------------------------  
   
 create or replace package ctx_api authid current_user is  
   
  -- API version  
  function version return varchar2 deterministic;  
   
  -- Thesaurus CTX API  
  -- Package constants  
  c_query_op_about constant varchar2(5) := 'about'; -- ABOUT query option  
  c_query_op_bt constant varchar2(2) := 'bt'; -- BT query option  
  c_query_op_nt constant varchar2(2) := 'nt'; -- NT query option  
  c_query_op_rt constant varchar2(2) := 'rt'; -- RT query option  
  c_query_op_syn constant varchar2(3) := 'syn'; -- SYN query option  
   
  c_refine_on constant number(1) := 1; -- Context refiner ON  
  c_refine_off constant number(1) := 0; -- Context refiner OFF  
   
  c_exp_detail_on constant number(1) := 1; -- Context expansion ON  
  c_exp_detail_off constant number(1) := 0; -- Context expansion OFF  
   
  c_nt_terms constant number(2) := 5; -- Expansion level stop quantity.  
                    -- Stop expansion level if NT's  
                    -- in subtree more than that constant.   
  -- CTX API types  
  type term_tab is table of varchar2(256) index by binary_integer;  
   
  -- Check phrase exists in specified thesaurus  
  function phrase_exists (p_phrase in varchar2,  
              p_thes_name in varchar2   
              default 'default') return boolean  
              deterministic;  
   
  -- Check relation exists  
  function phrase_relation_exists (p_phrase in varchar2,  
                  p_relation in varchar2  
                  default 'bt,btp,nt,ntp,rt,syn',  
                  p_thes_name in varchar2   
                  default 'default') return boolean  
                  deterministic;  
   
  -- NT/BT Expansion level definer  
  function search_expansion_level (p_phrase in varchar2,  
                  p_thes_name in varchar2   
                  default 'default') return number  
                  deterministic;  
   
  -- NT/BT Expansion level term  
  function search_expansion_term (p_phrase in varchar2,  
                  p_thes_name in varchar2   
                  default 'default') return varchar2  
                  deterministic;  
   
  -- Check term homographs  
  function has_homographs (p_phrase in varchar2,  
              p_thes_name in varchar2   
              default 'default') return boolean  
              deterministic;  
   
  -- Get homograph's term qualifiers  
  procedure get_qualifiers (p_qualifiers out ctx_api.term_tab,  
               p_phrase in varchar2,  
               p_thes_name in varchar2 default 'default');  
   
  -- Get scope note (SN) for phrase if it exists.  
  -- Otherwise returns empty string.  
  function get_note (p_phrase in varchar2,  
           p_thes_name in varchar2   
           default 'default') return varchar2  
           deterministic;  
   
  -- Get term BT  
  function get_bt (p_phrase in varchar2,  
          p_level in number default 1,  
          p_thes_name in varchar2 default 'default')   
          return varchar2 deterministic;  
   
  -- Get all term BT's  
  procedure get_bt (p_bt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default');  
   
  -- Get term NT's  
  procedure get_nt (p_nt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default');  
   
  -- Get term NTP's  
  procedure get_ntp (p_ntp out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default');  
   
  -- Get term RT's  
  procedure get_rt (p_rt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_thes_name in varchar2 default 'default');  
   
  -- Get term SYN's  
  procedure get_syn (p_syn out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_thes_name in varchar2 default 'default');  
   
  -- Universal parser   
  function search_string_parser (p_search_str in varchar2,  
                 p_query_mode in varchar2 default 'keyword',  
                 p_logical_op in varchar2 default 'and',  
                 p_query_opt in varchar2   
                 default ctx_api.c_query_op_about,  
                 p_expansion_level in number default 1,  
                 p_thes_name in varchar2 default 'default',  
                 p_refine_on in number   
                 default ctx_api.c_refine_off,  
                 p_exp_detail_on in number  
                 default ctx_api.c_exp_detail_off)   
                 return varchar2 deterministic;  
   
  -- Thesaurus content API  
  type thes_tab is table of varchar2(30) index by binary_integer;  
   
  -- Specified thesaurus term counter  
  function term_counter (p_thes_name in varchar2 default 'default')  
             return number deterministic;  
   
  -- Get loaded thesauruses  
  procedure thes_loaded (p_ths_list out ctx_api.thes_tab);  
   
  pragma restrict_references (default, wnds, wnps, rnds, rnps, trust);   
   
 end ctx_api;  
 /  
   
 show errors  
   
 -- Can be security issue.  
 -- Revoke exec privilege from public and grant  
 -- to only few users if you need.  
 -- Grant to public need only if package routines  
 -- calls from Web (modplsql).  
 grant execute on ctx_api to public  
 /  
   

В комментариях в принципе все задокументировано.

И - тело пакета prvtctxapi.pls:

 --------------------------------------------  
 --  PRIVATE IMPLEMENTATION (INTERNALS)  --  
 --    Yuri Voinov (C) 2004, 2016   --  
 --   MUST BE WRAPPED BEFORE LOAD !   --  
 --   Do not distribute this code !   --  
 --------------------------------------------  
 create or replace package body ctx_api is  
  -- --------------------------------------------------------------  
  type ReservedWordList is table of varchar2(8);  
  v_reserved ReservedWordList := ReservedWordList('ABOUT','ACCUM','BT','BTG','BTI','BTP','FUZZY','HASPATH',  
                  'INPATH','MDATA','MINUS','NEAR','NT','NTG','NTI','NTP','PT','RT','SQE',  
                  'SYN','TR','TRSYN','TT','WITHIN');  
   
  c_version constant varchar2(30) := '1.0.0.5'; -- API version  
   
  v_restab ctx_thes.exp_tab; -- BT's result table  
  v_restab2 ctx_thes.exp_tab; -- NT's result table  
   
  v_qualifier varchar2(256); -- Qualifier buffer  
  v_phrase varchar2(256);  -- Global phrase buffer  
   
  text_error exception; -- Uses for specified thesaurus exists check  
  term_has_homographs exception; -- Exception if term or phrase has homographs  
  pragma exception_init(text_error, -20000); -- Raised when specified thesaurus not loaded  
  phrase_error exception;  
  pragma exception_init(phrase_error, -20151); -- Phrase not found or thes not loaded.  
  -- --------------------------------------------------------------  
  procedure free_exp_memory is  
  /* INTERNAL USE */  
  /* Uses for clean out PL/SQL tables in expansion functions */  
  begin  
  v_restab.delete;  
  v_restab2.delete;  
  end free_exp_memory;  
  -- --------------------------------------------------------------  
  -- Return hardcoded API version  
  function version return varchar2 deterministic is  
  begin  
  return c_version;  
  end version;  
  -- --------------------------------------------------------------  
  function phrase_exists (p_phrase in varchar2,  
              p_thes_name in varchar2   
              default 'default') return boolean  
              deterministic is  
  v_phrase varchar2(512);   -- Phrase refine buffer  
  v_phrase_get varchar2(256); -- Get phrase buffer  
  begin  
  -- Refine p_phrase if contain qualifier  
  if instr(p_phrase,'(') > 0 and instr(p_phrase,')') > 0 then  
   v_phrase := trim(substr(p_phrase, 1, instr(p_phrase,'(')-1));  
  else  
   v_phrase := p_phrase;  
  end if;  
   
  select thp_phrase  
  into v_phrase_get  
  from ctx_thes_phrases  
  where thp_thesaurus = upper(p_thes_name)  
   and thp_phrase = upper(v_phrase);  
   
  return true;  
  exception  
  when too_many_rows then return true;  
  when no_data_found then return false;  
  when others then  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  end phrase_exists;  
  -- --------------------------------------------------------------  
  function phrase_relation_exists (p_phrase in varchar2,  
                  p_relation in varchar2  
                  default 'bt,btp,nt,ntp,rt,syn',  
                  p_thes_name in varchar2 default 'default')   
                  return boolean deterministic is  
  v_result boolean;  
  v_relation varchar2(50);  
  begin  
  v_relation := upper(p_relation);  
  return ctx_thes.has_relation(p_phrase, v_relation, p_thes_name);  
  exception  
  when text_error then  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  when others then  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end phrase_relation_exists;  
  -- --------------------------------------------------------------  
  function search_expansion_level (p_phrase in varchar2,  
                  p_thes_name in varchar2 default 'default')   
                  return number deterministic is  
  v_init_level number := 0; -- Initial expansion level  
  v_top_bt varchar2(255);  -- BT term buffer  
  v_nts pls_integer;    -- NT's counter  
  begin  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  loop  
   v_init_level := v_init_level + 1; -- Increment level  
   ctx_thes.bt(v_restab, v_phrase, v_init_level, p_thes_name); -- Get top BT for current level  
   v_top_bt := v_restab(v_restab.last).xphrase; -- Get category for current level  
   ctx_thes.nt(v_restab2, v_top_bt, v_init_level, p_thes_name); -- Check NT's  
   v_nts := v_restab2.count; -- Count NT's for current BT level  
  exit when v_nts >= ctx_api.c_nt_terms or v_restab(v_restab.last).xlevel = 0;  
  end loop;  
  return v_restab(v_restab.last).xlevel; -- Return xlevel  
  free_exp_memory; -- Clean out PL/SQL tables  
  exception  
  when text_error then  
   free_exp_memory; -- Clean out PL/SQL tables  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  when others then  
   free_exp_memory; -- Clean out PL/SQL tables  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end search_expansion_level;  
  -- --------------------------------------------------------------  
  function search_expansion_term (p_phrase in varchar2,  
                  p_thes_name in varchar2 default 'default')   
                  return varchar2 deterministic is  
  v_init_level number := 0; -- Initial expansion level  
  v_top_bt varchar2(255); -- BT term buffer  
  v_nts pls_integer;    -- NT's counter  
  begin  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  loop  
   v_init_level := v_init_level + 1; -- Increment level  
   ctx_thes.bt(v_restab, v_phrase, v_init_level, p_thes_name); -- Get top BT for current level  
   v_top_bt := v_restab(v_restab.last).xphrase; -- Get category for current level  
   ctx_thes.nt(v_restab2, v_top_bt, v_init_level, p_thes_name); -- Check NT's  
   v_nts := v_restab2.count; -- Count NT's for current BT level  
  exit when v_nts >= ctx_api.c_nt_terms or v_restab(v_restab.last).xlevel = 0;  
  end loop;  
  return v_top_bt; -- Return BT term  
  free_exp_memory; -- Clean out PL/SQL tables  
  exception  
  when text_error then  
   free_exp_memory; -- Clean out PL/SQL tables  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  when others then  
   free_exp_memory; -- Clean out PL/SQL tables  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end search_expansion_term;  
  -- --------------------------------------------------------------  
  function has_homographs (p_phrase in varchar2,  
              p_thes_name in varchar2 default 'default')   
              return boolean deterministic is  
  v_phrase_tmp varchar2(256);  
  v_qualifier varchar2(256);  
  begin  
  if instr(p_phrase,'(') = 0 and instr(p_phrase,')') = 0 then  
   -- If p_phrase not contains qualifiers, get it as is.  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  else  
   -- If p_phrase contains qualifiers, lets remove qualifier  
   -- from open "(" to the end of line.  
   v_phrase := substr(trim(p_phrase), 1, instr(trim(p_phrase),'(') - 2);  
  end if;  
  select ctxsys.ctx_thes_phrases.thp_phrase,  
      ctxsys.ctx_thes_phrases.thp_qualifier -- Get phrase and qualifier from thesauri  
  into v_phrase_tmp, v_qualifier  
  from ctxsys.ctx_thes_phrases -- View ctx_thes_phrase in schema CTXSYS  
  where ctxsys.ctx_thes_phrases.thp_thesaurus = upper(p_thes_name)  
   and ctxsys.ctx_thes_phrases.thp_phrase = upper(v_phrase);  
  if v_phrase_tmp is not null or v_qualifier is null   
   then return false; -- Return false if term only one or has no qualifiers  
  end if; -- (but if no qualifiers and term more than once, raises exception)  
  exception  
  when too_many_rows then  
   return true; -- If select returns more than one rows, homographs exists  
  when no_data_found then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end has_homographs;  
  -- --------------------------------------------------------------  
  function get_note (p_phrase in varchar2,  
           p_thes_name in varchar2   
           default 'default') return varchar2  
           deterministic is  
  -- Get scope note (SN) for phrase if SN exists.  
  -- Otherwise returns empty string.  
  v_phrase varchar2(512);   -- Phrase buffer  
  v_qualifier varchar2(256); -- Qualifier buffer  
  v_note varchar2(2000) := '';  
  phrase_not_exists exception;  
  begin  
  -- Check phrase existence  
  if not ctx_api.phrase_exists(p_phrase, p_thes_name) then  
   raise phrase_not_exists;  
  end if;  
  -- Refine p_phrase if contain qualifier  
  if instr(p_phrase,'(') > 0 and instr(p_phrase,')') > 0 then  
   v_phrase := trim(substr(p_phrase, 1, instr(p_phrase,'(')-1));  
   v_qualifier := trim(substr(p_phrase, instr(p_phrase,'('), instr(p_phrase,')')-1));  
   -- Selet note for phrase with qualifier  
   select thp_scope_note  
   into v_note  
   from ctx_thes_phrases  
   where thp_thesaurus = upper(p_thes_name)  
    and thp_phrase = upper(v_phrase)  
    and (thp_qualifier is not null   
      and thp_qualifier = upper(v_qualifier));  
  else  
   v_phrase := p_phrase;  
   -- Selet note for phrase without qualifier  
   select thp_scope_note  
   into v_note  
   from ctx_thes_phrases  
   where thp_thesaurus = upper(p_thes_name)  
    and thp_phrase = upper(v_phrase);  
  end if;  
  return v_note; -- Return SN  
  exception  
  -- Catch both exceptions. Function produces too_many_rows  
  -- exception on some thesauri data.  
  when no_data_found or too_many_rows then return null;  
  when phrase_not_exists then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  end get_note;  
  -- --------------------------------------------------------------  
  procedure get_qualifiers (p_qualifiers out ctx_api.term_tab,  
               p_phrase in varchar2,  
               p_thes_name in varchar2 default 'default') is  
  v_qualifiers ctx_api.term_tab;  
  not_found exception;  
  begin  
  if instr(p_phrase,'(') = 0 and instr(p_phrase,')') = 0 then  
   -- If p_phrase not contains qualifiers, get it as is.  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  else  
   -- If p_phrase contains qualifiers, lets remove qualifier  
   -- from open "(" to the end of line.  
   v_phrase := substr(trim(p_phrase), 1, instr(trim(p_phrase),'(') - 2);  
  end if;  
  select ctxsys.ctx_thes_phrases.thp_qualifier -- Bulk collect qualifiers for term p_phrase  
  bulk collect into v_qualifiers  
  from ctxsys.ctx_thes_phrases  
  where ctxsys.ctx_thes_phrases.thp_thesaurus = upper(p_thes_name)  
   and ctxsys.ctx_thes_phrases.thp_phrase = upper(v_phrase);  
  if sql%notfound then raise not_found; end if; -- If phrase not found, raise exception  
  p_qualifiers := v_qualifiers; -- Return qualifiers  
  v_qualifiers.delete;     -- Free memory  
  exception  
  when not_found then  
   v_qualifiers.delete; -- Free memory  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_qualifiers.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_qualifiers;  
  -- --------------------------------------------------------------  
  function get_bt (p_phrase in varchar2,  
          p_level in number default 1,  
          p_thes_name in varchar2 default 'default')   
          return varchar2 deterministic is  
  v_res ctx_thes.exp_tab; -- ctx_thes.bt result buffer  
  v_ret varchar2(256); -- Return buffer  
  begin -- Check phrase for homographs if qualifier is absent  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  if instr(v_phrase,'(') = 0 and instr(v_phrase,')') = 0 then  
   if ctx_api.has_homographs (v_phrase, p_thes_name) then  
   raise term_has_homographs;  
   end if;  
  end if;  
  ctx_thes.bt(v_res, v_phrase, p_level, p_thes_name); -- Get BT's level p_level  
  for i in v_res.last..v_res.last -- Extract only LAST BT  
  loop  
   v_ret := v_res(i).xphrase;  
  end loop;  
  return v_ret; -- Return BT  
  v_res.delete; -- Free memory  
  exception   
  when term_has_homographs then  
   raise_application_error(-20152,'Phrase "'||upper(p_phrase)||'" has homographs.');  
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_bt;  
  -- --------------------------------------------------------------  
  procedure get_bt (p_bt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default') is  
  v_res ctx_thes.exp_tab; -- ctx_thes.bt result buffer  
  begin  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  ctx_thes.bt(v_res, v_phrase, p_level, p_thes_name); -- Get BT's level p_level  
  if v_res.count = 1 then p_bt(1) := v_res(1).xphrase; -- If BT's absent, return phrase.  
  else -- If phrase has BT's, then return them all, exclude top - original phrase  
   for i in 2..v_res.last -- Extract only BT's  
   loop  
   p_bt(i-1) := v_res(i).xphrase; -- Let's count return array from 1  
   end loop;  
  end if;  
  v_res.delete; -- Free memory  
  exception   
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_bt;  
  -- --------------------------------------------------------------  
  procedure get_nt (p_nt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default') is  
  v_res ctx_thes.exp_tab; -- ctx_thes.nt result buffer  
  begin -- Check phrase for homographs if qualifier is absent  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  if instr(v_phrase,'(') = 0 and instr(v_phrase,')') = 0 then  
   if ctx_api.has_homographs (v_phrase, p_thes_name) then  
   raise term_has_homographs;  
   end if;  
  end if;  
  ctx_thes.nt(v_res, v_phrase, p_level, p_thes_name); -- Get NT's level p_level  
  if v_res.count = 1 then p_nt(1) := v_res(1).xphrase; -- If NT's absent, return phrase.  
  else -- If phrase has NT's, then return them all, exclude top - original phrase  
   for i in 2..v_res.last -- Extract only NT's  
   loop  
   p_nt(i-1) := v_res(i).xphrase; -- Let's count return array from 1  
   end loop;  
  end if;  
  v_res.delete; -- Free memory  
  exception   
  when term_has_homographs then  
   raise_application_error(-20152,'Phrase "'||upper(p_phrase)||'" has homographs.');  
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_nt;  
  -- --------------------------------------------------------------  
  procedure get_ntp (p_ntp out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_level in number default 1,  
           p_thes_name in varchar2 default 'default') is  
  v_res ctx_thes.exp_tab; -- ctx_thes.ntp result buffer  
  begin -- Check phrase for homographs if qualifier is absent  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  if instr(v_phrase,'(') = 0 and instr(v_phrase,')') = 0 then  
   if ctx_api.has_homographs (v_phrase, p_thes_name) then  
   raise term_has_homographs;  
   end if;  
  end if;  
  ctx_thes.ntp(v_res, v_phrase, p_level, p_thes_name); -- Get NTP's level p_level  
  if v_res.count = 1 then p_ntp(1) := v_res(1).xphrase; -- If NTP's absent, return phrase.  
  else -- If phrase has NTP's, then return them all, exclude top - original phrase  
   for i in 2..v_res.last -- Extract only NTP's  
   loop  
   p_ntp(i-1) := v_res(i).xphrase; -- Let's count return array from 1  
   end loop;  
  end if;  
  v_res.delete; -- Free memory  
  exception   
  when term_has_homographs then  
   raise_application_error(-20152,'Phrase "'||upper(p_phrase)||'" has homographs.');  
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_ntp;  
  -- --------------------------------------------------------------  
  procedure get_rt (p_rt out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_thes_name in varchar2 default 'default') is  
  v_res ctx_thes.exp_tab; -- ctx_thes.rt result buffer  
  begin -- Check phrase for homographs if qualifier is absent  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  if instr(v_phrase,'(') = 0 and instr(v_phrase,')') = 0 then  
   if ctx_api.has_homographs (v_phrase, p_thes_name) then  
   raise term_has_homographs;  
   end if;  
  end if;  
  ctx_thes.rt(v_res, v_phrase, p_thes_name); -- Get RT's  
  if v_res.count = 1 then p_rt(1) := v_res(1).xphrase; -- If RT's absent, return phrase.  
  else -- If phrase has RT's, then return them all, exclude top - original phrase  
   for i in 2..v_res.last -- Extract only RT's  
   loop  
   p_rt(i-1) := v_res(i).xphrase; -- Let's count return array from 1  
   end loop;  
  end if;  
  v_res.delete; -- Free memory  
  exception   
  when term_has_homographs then  
   raise_application_error(-20152,'Phrase "'||upper(p_phrase)||'" has homographs.');  
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_rt;  
  -- --------------------------------------------------------------  
  procedure get_syn (p_syn out ctx_api.term_tab,  
           p_phrase in varchar2,  
           p_thes_name in varchar2 default 'default') is  
  v_res ctx_thes.exp_tab; -- ctx_thes.syn result buffer  
  begin -- Check phrase for homographs if qualifier is absent  
  v_phrase := trim(p_phrase); -- Trim leading and trailing spaces  
  if instr(v_phrase,'(') = 0 and instr(v_phrase,')') = 0 then  
   if ctx_api.has_homographs (v_phrase, p_thes_name) then  
   raise term_has_homographs;  
   end if;  
  end if;  
  ctx_thes.syn(v_res, v_phrase, p_thes_name); -- Get SYN's  
  if v_res.count = 1 then p_syn(1) := v_res(1).xphrase; -- If SYN's absent, return phrase.  
  else -- If phrase has SYN's, then return them all, exclude top - original phrase  
   for i in 2..v_res.last -- Extract only SYN's  
   loop  
   p_syn(i-1) := v_res(i).xphrase; -- Let's count return array from 1  
   end loop;  
  end if;  
  v_res.delete; -- Free memory  
  exception   
  when term_has_homographs then  
   raise_application_error(-20152,'Phrase "'||upper(p_phrase)||'" has homographs.');  
  when phrase_error then  
   raise_application_error(-20151,'Phrase "'||  
               upper(p_phrase)||'" not exist or specified thesaurus "'||  
               upper(p_thes_name)||'" not loaded.');  
  when others then  
   v_res.delete; -- Free memory  
   raise_application_error(-20155,'Error ORA' || SQLCODE || ' raised.');  
  end get_syn;  
  -- --------------------------------------------------------------  
  function search_string_parser (p_search_str in varchar2,  
                 p_query_mode in varchar2 default 'keyword',  
                 p_logical_op in varchar2 default 'and',  
                 p_query_opt in varchar2   
                 default ctx_api.c_query_op_about,  
                 p_expansion_level in number default 1,  
                 p_thes_name in varchar2 default 'default',  
                 p_refine_on in number   
                 default ctx_api.c_refine_off,  
                 p_exp_detail_on in number  
                 default ctx_api.c_exp_detail_off)   
                 return varchar2 deterministic is  
  -- --------------------------------------------  
  -- p_query_opt must be:  
  -- about (c_query_op_about)  
  -- bt (c_query_op_bt)  
  -- nt (c_query_op_nt)  
  -- rt (c_query_op_rt)  
  -- syn (c_query_op_syn)  
  -- p_expansion_level must be positive when   
  --          specified.  
  -- p_thes_name can be <= 30 characters.  
  -- Known issues:  
  -- 1. p_thes_name cannot contain "_" symbols,   
  --  it will be removed from result string!  
  -- 2. If p_exp_detail_on is enabled,  
  --  p_expansion_level value will be ignored.  
  -- 3. If p_query_opt in ('about','syn','rt') or  
  --  p_query_mode = 'keyword',  
  --  p_exp_detail_on will be ignored.  
  -- --------------------------------------------  
  type token_tab is table of varchar2(4000) index by binary_integer;  
  v_token token_tab; -- Token table  
   
  type exp_lvl_tab is table of number index by binary_integer;  
  v_exp_lvl exp_lvl_tab; -- Expansion levels table  
    
  type v_tt_rec is record (v_tt_token varchar2(4000),   
              v_tt_cnt number); -- TT record type  
  type v_tt_tab is table of v_tt_rec index by binary_integer; -- TT table type  
   
  v_tt v_tt_tab;      -- TT table  
  v_count pls_integer;   -- TT tokens counter  
  v_count2 pls_integer;  -- TT tokens counter 2  
  c_refine_level constant pls_integer := 1; -- Refine level constant. Higher value  
                       -- means more supercategories occurences.  
                       -- Minimum value is 1!  
  /* Note: TT is the Top Term abbreviation */  
  -- v_temp varchar2(4000); -- DEBUG  
  -- v_temp2 number;    -- DEBUG  
   
  v_buffer varchar2(4000); -- Search string buffer  
   
  v_temp_value varchar2(4000); -- Parser variables  
  v_temp_value2 varchar2(4000);  
  v_quotes pls_integer;    -- Quotation variable  
  n pls_integer;        -- Quotation parser counter  
  i pls_integer;        -- Common parser counter  
  j pls_integer;        -- Common final string forming counter  
   
  -- Note: Cannot call procedure hr_show_doc from javascript   
  -- when pass symbolic logical operators into parsed string  
  v_query_mode varchar2(10); -- Query mode buffer  
  c_or_operator constant varchar2(5) := ' or ';  -- OR operator  
  c_and_operator constant varchar2(5) := ' and '; -- AND operator  
  v_operator varchar2(5) := c_and_operator; -- Operator buffer, default AND  
  v_op_fin varchar2(5);           -- Final op buffer  
  v_exp_detail_on number(1) := p_exp_detail_on; -- Expansion detail flag buffer  
   
  v_ext1 varchar2(6); -- Open thes extension  
  v_ext2 varchar2(50); -- Close thes extension (including level and  
            -- thesaurus name)  
  v_expansion_level varchar2(10); -- Expansion functions level  
  v_thes_name varchar2(30);    -- Thesaurus name buffer  
   
  -- --------------------------------------------------------------  
  -- Delete unneeded delimiters in whole token  
  function del_delm(p_str in varchar2) return varchar2 is  
  v_str varchar2(4000);  
  begin  
  v_str := replace(p_str,',','');  
  while instr(v_str,' ') > 0 loop -- Remove unneeded spaces  
   v_str := replace(v_str,' ',' ');  
  end loop;  
  return v_str;  
  end del_delm;  
   
  ---- Escape key word in search string  
  function escape_key_word(p_str in varchar2) return varchar2 is  
  v_str varchar2(4000) := p_str; -- Init variable with parameter  
  begin  
  -- Escape all keywords  
  for i in v_reserved.first..v_reserved.last loop  
   v_str := replace(v_str,lower(v_reserved(i)),'{'||lower(v_reserved(i))||'}');  
  end loop;  
  return v_str;  
  end escape_key_word;  
   
  -- Remove key word in search string  
  function remove_key_word(p_str in varchar2) return varchar2 is  
  v_str varchar2(4000) := p_str; -- Init variable with parameter  
  begin  
  -- Remove all keywords  
  for i in v_reserved.first..v_reserved.last loop  
   v_str := replace(v_str,lower(v_reserved(i)),'');  
  end loop;  
  return v_str;  
  end remove_key_word;  
   
  -- Free tokens memory  
  procedure free_memory is  
  /* Uses for clean out PL/SQL tables in parser function */  
  begin  
  v_token.delete;  
  v_tt.delete;  
  if v_exp_detail_on = ctx_api.c_exp_detail_on then  
   v_exp_lvl.delete;  
  end if;  
  exception  
  when others then null;  
  end free_memory;  
   
  ----  
  begin /* MAIN BLOCK */  
  -- -----------------------------------------  
  -- Threat null search string  
  if nvl(length(p_search_str),0) = 0 then return null; end if;  
  -- Lowercase and trim spaces  
  v_buffer := trim(lower(p_search_str));  
  -- -----------------------------------------  
  -- Check query mode. If cannot identify, set KEYWORD by default.  
  if lower(p_query_mode) = 'keyword' or   
    lower(p_query_mode) = 'concept' then  
   v_query_mode := lower(p_query_mode);  
  else v_query_mode := 'keyword';  
  end if;  
  -- -----------------------------------------  
  -- Threat query mode  
  if v_query_mode = 'concept' then -- Check search string for keywords  
   v_buffer := escape_key_word(v_buffer); -- In CONCEPT mode ESCAPE keywords  
  else   
   v_buffer := remove_key_word(v_buffer); -- In KEYWORD mode REMOVE keywords  
   v_exp_detail_on := ctx_api.c_exp_detail_off; -- Turn off exp_detail in KEYWORD mode  
  end if;  
  -- -----------------------------------------  
  -- Threat logical operator  
  if lower(p_logical_op) = 'and' or instr(p_logical_op,'&') > 0 then  
   v_operator := c_and_operator;  
  elsif lower(p_logical_op) = 'or' or instr(p_logical_op,'|') > 0 then  
   v_operator := c_or_operator;  
  else               
   v_operator := c_or_operator;  
  end if;  
  -- -----------------------------------------  
  -- Threat ABOUT, RT, SYN and Expansion level flag  
  if (substr(lower(p_query_opt),1,2) = 'ab' or   
    substr(lower(p_query_opt),1,2) = 'rt' or   
    substr(lower(p_query_opt),1,2) = 'sy') then  
   v_exp_detail_on := ctx_api.c_exp_detail_off; -- Turn off exp_detail in ABOUT,RT,SYN mode  
  end if;  
  -- -----------------------------------------  
  /* First and last symbol check and clean */  
  if substr(v_buffer,1,1) = '|' then v_buffer := trim(both '|' from v_buffer);  
   elsif substr(v_buffer,1,1) = '&' then v_buffer := trim(both '&' from v_buffer);  
   elsif substr(v_buffer,1,1) = '~' then v_buffer := trim(both '~' from v_buffer);  
   elsif substr(v_buffer,1,1) = ',' then v_buffer := trim(both ',' from v_buffer);  
   elsif substr(v_buffer,1,1) = '=' then v_buffer := trim(both '=' from v_buffer);  
  end if;  
  -- -----------------------------------------  
  -- Clean search string from punctuation, garbage and DR engine keywords  
  v_buffer := trim(replace(v_buffer,'|', ' ')); -- Replace any search meta to space  
  v_buffer := trim(replace(v_buffer,'~', ' '));  
  v_buffer := trim(replace(v_buffer, '`', ' '));  
  v_buffer := trim(replace(v_buffer, '$', ' ')); -- STEM op  
  v_buffer := trim(replace(v_buffer, '>', ' ')); -- threshold op  
  v_buffer := trim(replace(v_buffer, '*', ' ')); -- weight op  
   
  v_buffer := trim(replace(v_buffer,':',' '));  
  v_buffer := trim(replace(v_buffer,';',' '));  
  v_buffer := trim(replace(v_buffer,':'',',' '));  
  v_buffer := trim(replace(v_buffer,'!',' ')); -- SOUNDEX op  
  v_buffer := trim(replace(v_buffer,'()','')); -- Suppress empty parenthes  
  v_buffer := trim(replace(v_buffer,'[]','')); -- Suppress empty parenthes  
  v_buffer := trim(replace(v_buffer,'{}','')); -- Suppress empty parenthes  
  v_buffer := trim(replace(v_buffer,'?',' ')); -- FUZZY equiv  
  v_buffer := trim(replace(v_buffer,'&',' ')); -- Must be before ',' threat  
  v_buffer := trim(replace(v_buffer,'+',' ')); -- ACCUM equiv  
  v_buffer := trim(replace(v_buffer,'\',' '));  
  --v_buffer := trim(replace(v_buffer,'-',' ')); -- This is printjoin char   
  v_buffer := trim(replace(v_buffer,',',' ')); -- Threat ','  
  v_buffer := trim(replace(v_buffer,'.',' '));  
  v_buffer := trim(replace(v_buffer,' and ',' ')); -- Remove logical ops  
  v_buffer := trim(replace(v_buffer,' or ',' ')); -- Remove logical ops  
  v_buffer := trim(replace(v_buffer,'""','" "')); -- Correct double quotation  
  -- -----------------------------------------  
   
  -- Parse '"' pairs  
  v_quotes := length(v_buffer)-length(replace(v_buffer,'"', ''));  
  -- if there are quotes and the quotes are balanced, we'll extract that  
  -- terms "as is" and save them into a phrases array  
  if (v_quotes > 0 or mod(v_quotes,2) = 0) then  
   -- If this predicate be AND, odd '"' will be  
   -- ignored all '"' pairs in next parsing stage  
   v_temp_value2 := v_buffer;  
   for i in 1..v_quotes/2 loop  
    n := instr(v_temp_value2,'"');  
    v_temp_value := v_temp_value || substr(v_temp_value2,1,n-1);  
    v_temp_value2 := substr(v_temp_value2,n+1);  
    n := instr(v_temp_value2,'"');  
    v_token(i) := trim(substr(v_temp_value2,1,n-1));  
    v_temp_value2 := substr(v_temp_value2,n+1);  
   end loop;  
    v_temp_value := v_temp_value || v_temp_value2;  
  else  
   v_temp_value := v_buffer;  
  end if;  
   
  v_buffer := v_temp_value; -- Let's parse next...  
     
  -- dbms_output.put_line('Rest string: '||v_temp_value); -- Debug output  
   
  -- Threat single phrase exception  
  if trim(v_buffer) is null and v_token.count > 0 then  
   goto Final;  
  end if;  
   
  -- Parse and get tokens in the rest string ...  
  if instr(v_buffer,' ') > 0 then -- Look ' ' delimiters  
   if v_token.count = 0 then i := 1;  
   else i := v_token.last + 1; -- Continue parsing  
   end if;  
   while instr(v_buffer,' ') > 0 loop  
   v_token(i) := substr(v_buffer,1,instr(v_buffer,' ')-1);  
   i := i + 1; -- Increment counter  
   v_buffer := substr(v_buffer,instr(v_buffer,' ')+1);  
   v_buffer := trim(v_buffer);  
   end loop;  
   -- Get the last token  
   if substr(v_buffer,length(v_buffer))='\' or  
    substr(v_buffer,length(v_buffer))='?' or  
    substr(v_buffer,length(v_buffer))=';' then  
   v_buffer :=  
    substr(v_buffer,1,length(v_buffer)-1)||'\'||  
    substr(v_buffer,length(v_buffer));  
   end if;  
   v_token(i):=v_buffer;  
  else  
   -- Only one word was in search string  
   if substr(v_buffer,length(v_buffer))='\' or  
    substr(v_buffer,length(v_buffer))='?' or  
    substr(v_buffer,length(v_buffer))=';' then  
   v_buffer := substr(v_buffer,1,length(v_buffer)-1)||'\'||  
         substr(v_buffer,length(v_buffer));  
   end if;  
   v_token(1) := v_buffer;  
  end if;  
  -- Parsing complete!  
   
  /* ====================== */  
  /* Query Expansion Detail */  
  /* ====================== */  
  -- Check and assign expansion level depends query option  
  -- (not used when ABOUT, SYN or RT specified)  
  -- Note: Only hierarchical relations has expansion levels!  
  if substr(lower(p_query_opt),1,2) <> 'ab' then  
   -- Check and assign thesaurus name  
   if substr(p_thes_name,1,30) = 'default' then  
   v_thes_name := null;  
   else  
   v_thes_name := ','||p_thes_name;  
   end if;  
   if (substr(lower(p_query_opt),1,2) = 'nt' or  -- If p_exp_detail_on is ON  
     substr(lower(p_query_opt),1,2) = 'bt') and -- and uses NT/BT query option  
     v_exp_detail_on = ctx_api.c_exp_detail_on -- and query expansion is ON...  
   then                      -- ... then expansion level up.  
   for v_count in v_token.first..v_token.last loop -- Init expansion levels array  
    v_exp_lvl(v_count) := ctx_api.search_expansion_level(v_token(v_count), p_thes_name);  
   end loop;  
   elsif (p_expansion_level = 0 or -- Also check zero (TT level) expansion level  
      p_expansion_level = 1 or  
      p_expansion_level < 0) or  
      (substr(lower(p_query_opt),1,2) <> 'nt' and  
      substr(lower(p_query_opt),1,2) <> 'bt') and  
      v_exp_detail_on = ctx_api.c_exp_detail_off  
   then v_expansion_level := null; -- Default expansion level  
   else  
   v_expansion_level := ','||to_char(p_expansion_level); -- set the same expansion level  
   end if;  
  elsif substr(lower(p_query_opt),1,2) = 'ab' then  
   -- ABOUT option not uses thesaurus and level  
   v_expansion_level := null;  
   v_thes_name := null;  
  end if;  
  /* ====================== */  
  /* Query Expansion Detail */  
  /* ====================== */  
   
  /* ===================== */  
  /* Query Context Refiner */  
  /* ===================== */  
  -- Refine query context using specified thesaurus  
  -- If query mode is CONCEPT and refine is enabled...  
  if v_query_mode = 'concept' and p_refine_on = 1 then  
   -- Get top terms for tokens in CONCEPT mode ...  
   for v_count in v_token.first..v_token.last loop  
   v_tt(v_count).v_tt_token := ctx_thes.tt(v_token(v_count), p_thes_name);  
   end loop;  
   -- Count TT's  
   for v_count in v_tt.first..v_tt.last loop  
   v_tt(v_count).v_tt_cnt := 0;  
   for v_count2 in v_tt.first..v_tt.last loop  
    if v_tt(v_count).v_tt_token = v_tt(v_count2).v_tt_token then  
    v_tt(v_count).v_tt_cnt := v_tt(v_count).v_tt_cnt + 1;  
    end if;  
   end loop;  
   end loop;  
   -- Check if all TT's unique and refine result if not  
   for v_count in v_tt.first..v_tt.last loop  
   -- For category counter=c_refine_level...  
   if v_tt(v_count).v_tt_cnt = c_refine_level then  
    v_token(v_count) := ''; -- ...delete source token!  
   end if;  
   end loop;  
  end if;  
  /* ===================== */  
  /* Query Context Refiner */  
  /* ===================== */  
   
  -- DEBUG output  
  -- dbms_output.put_line('TT''s:');  
  -- for v_count in v_token.first..v_token.last loop  
  -- v_temp := v_tt(v_count).v_tt_token;  
  -- v_temp2 := v_tt(v_count).v_tt_cnt;  
  -- dbms_output.put_line(v_temp||' '||v_temp2);  
  -- end loop;  
  -- dbms_output.put_line('TT Tokens in array:'||v_tt.count); -- Debug output  
  -- dbms_output.put_line('Tokens in array:'||v_token.count); -- Debug output  
  -- dbms_output.put_line('Exp levels in array:'||v_exp_lvl.count); -- Debug output  
   
  <<Final>>  
  loop  
  if v_query_mode = 'keyword' then  
   v_ext1 := '';  
   v_ext2 := '';  
  else  
   v_ext1 :=   
   case substr(lower(p_query_opt),1,2)  
   when 'ab' then 'about('  
   when 'bt' then 'bt('  
   when 'nt' then 'nt('  
   when 'rt' then 'rt('  
   when 'sy' then 'syn('  
   end; -- End case  
   if v_exp_detail_on = ctx_api.c_exp_detail_off then  
   v_ext2 := v_expansion_level||v_thes_name||')';  
   end if;  
  end if;  
  -- Threat final string  
  v_buffer := null;  
  for j in v_token.first..v_token.last loop  
   if substr(v_token(j),1,1) = '(' then  
   -- If token is single word in () (i.e.(cat)),  
   -- we'll ignore this token  
   v_token(j) := null;  
   end if;  
   if instr(v_token(j),'(') > 0 then -- Threat parenthes in qualifiers  
   v_token(j) := replace(v_token(j),'(',' (');  
   end if;  
   v_op_fin := v_operator;  
   if v_buffer is null then v_op_fin := null; end if; -- Start line?  
   -- Skip empty token entries. This also remove tokens cutted with context refiner.  
   if v_token(j) is not null then  
   if v_exp_detail_on = ctx_api.c_exp_detail_off then  -- Expansion OFF  
    v_buffer := v_buffer||v_op_fin||v_ext1||'{'||del_delm(v_token(j))||'}'||v_ext2;  
   elsif v_exp_detail_on = ctx_api.c_exp_detail_on then -- Expansion ON  
    v_buffer := v_buffer||v_op_fin||v_ext1||'{'||del_delm(v_token(j))||'}'||','||v_exp_lvl(j)||v_thes_name||')';  
   end if;  
   end if;   
  end loop;  
  exit when 1=1; -- Only one labeled loop  
  end loop;  
   
  -- Finally release token tables memory  
  free_memory;  
   
  -- -----------------------------------------  
  -- Wildcards must be removed in CONCEPT mode  
  if (instr(v_buffer,'%') > 0 or instr(v_buffer,'_') > 0)  
    and v_query_mode = 'concept' then  
  v_buffer := replace(v_buffer,'%','');  
  v_buffer := replace(v_buffer,'_','');  
  -- Workaround bug 10029.1  
  elsif (instr(v_buffer,'%') > 0 or instr(v_buffer,'_') > 0)  
     and v_query_mode = 'keyword' then  
  v_buffer := replace(v_buffer,'{','');  
  v_buffer := replace(v_buffer,'}','');  
  end if;  
  -- -----------------------------------------  
   
  return v_buffer;  
   
  exception  
  when text_error then  
   free_memory; -- Release memory and exit if text_error exception occurs  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  when others then   
   free_memory; -- When any others error, let's release memory  
   return null; -- and exit from function with null return  
  end search_string_parser; /* END MAIN BLOCK */  
  -- --------------------------------------------------------------  
  function term_counter (p_thes_name in varchar2 default 'default')  
             return number deterministic is  
  -- Specified thesaurus term counter  
  v_count number;  
  begin  
  select count(*)  
  into v_count  
  from ctx_thes_phrases  
  where thp_thesaurus = upper(p_thes_name)  
  group by thp_thesaurus;  
  return v_count;  
  exception  
  when no_data_found then  
   raise_application_error(-20150,'Oracle Text error. Possible specified thesaurus not loaded.');  
  end term_counter;  
  -- --------------------------------------------------------------  
  procedure thes_loaded (p_ths_list out ctx_api.thes_tab) is  
  -- Get loaded thesauruses  
  begin  
  select ths_name  
  bulk collect into p_ths_list  
  from ctx_thesauri;  
  exception  
  when no_data_found then  
   raise_application_error(-20154,'No thesaurus found.');  
  end thes_loaded;  
  -- --------------------------------------------------------------  
 end ctx_api;  
 /  
   
 show errors  
   

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

Принцип работы собственно парсера:
  1. Поисковый запрос очищается от мусора и ключевых слов
  2. Обрабатываются особые и граничные случаи элементов запроса
  3. В зависимости от выбранного режима работы парсера выполняется:
    1. Либо разбивка непосредственно на токены с обработкой заключенных в кавычки слов и словосочетаний - они выделяются в отдельные токены и обрабатываются как точное соответствие
    2. Либо дополнительно происходит выделение токенов с квалификаторами (подобно тому, как это реализовано в Википедии) при использовании тезаурусов, могущих содержать омонимы/гомографы
  4. В случае, если выбран режим уточнения запроса и имеется загруженный тезаурус, производится тематический анализ принадлежности токенов одной теме на основе тезауруса и отбрасывание токенов, тематически не относящихся к количественно доминирующей в токенах запроса тематике.
  5. Затем из распарсенных токенов, после вышеприведенной обработки, снова собирается строка запроса и возвращается функцией для передачи непосредственно в Oracle Text и последующего исполнения.
Немного дополнительной информации по применению:

 ВНИМАНИЕ: Пакет использует процедуры пакета CTX_THES.  Для  
 корректной загрузки и работы пакета необходимо дать прямой  
 грант на пакет CTX_THES:  
   
 grant execute on ctx_thes to <user>;  
   
 Гранта роли CTXAPP недостаточно! (роль грантуется целевой  
 схеме установочным скриптом ввиду необходимости доступа  
 функций пакета к представлениям схемы CTXSYS)  
   
 Замечание: Данный грант автоматически дается при установке  
 парсера скриптом inst_parser.*  
   
 Замечание 1: Если парсер выполняются в режиме, использующим  
 тезаурус  (CONTEXT  query  mode/уточнение  контекста  
 по тезаурусу), при отсутствии в базе данных загруженного  
 тезауруса,  возбуждается  ошибка  ORA-20150.  
 Остальные подпрограммы пакета также используют тезаурус  
 и при отсутствии загруженного тезауруса возбуждают ошибку  
 20150. Следует иметь в виду, что тезаурус не обязательно  
 имеет имя DEFAULT, в этом случае при вызове подпрограмм  
 (включая парсер) необходимо определять имя используемого  
 тезауруса.  
   
 Замечание 2:   Константа ctx_nt_terms пакета определяет  
 количество терминов NT уровня иерархии тезауруса, на котором  
 следует  остановиться  при  выполнении  автоматического  
 расширения иерархических функций BT/NT функциями пакета  
 search_expansion_level, search_expansion_term,  
 search_string_parser.   
 По  умолчанию  функции останавливаются на том уровне,  
 BT которого  содержит свыше 5 терминов NT. Как правило,  
 этого достаточно для типовых применений. Если значения  
 по умолчанию не достаточно, необходимо изменить константу  
 (отредактировав спецификацию пакета) и перезагрузить пакет  
 (спецификацию и тело).  
   
 Замечание 3:  Разумеется, до установки пакета база данных  
 должна содержать установленную опцию Oracle Text (ConText).  
 Пакет содержит определения типов данных на основе типов  
 Oracle Text. При выполнении установки пакета выполняется  
 проверка существования Oracle Text в целевой БД.  
   
 Особенности поведения:  
 ======================  
   
 1. Параметр p_thes_name не может содержать символы "_", они  
 будут  удалены  из  результирующей строки, возвращаемой  
 парсером.  
   
 2. Если параметр p_exp_detail_on включен (равен 1), параметр  
 p_expansion_level будет игнорироваться.  
   
 3. Если режим расширения запроса p_query_opt='about','syn'  
 или  'rt',  либо если p_query_mode = 'keyword' (поиск  
 по  ключевым  словам),  параметр  p_exp_detail_on  will  
 игнорируется.  
   
   

Упомянутый выше скрипт установки inst_api.sql:

 rem --------------------------------------------------------------------------  
 rem -- PROJECT_NAME: CTX_API                        --  
 rem -- RELEASE_VERSION: 1.0.0.5                       --  
 rem -- RELEASE_STATUS: Release                       --  
 rem --                                   --  
 rem -- REQUIRED_ORACLE_VERSION: 10.1.0.x                  --  
 rem -- MINIMUM_ORACLE_VERSION: 9.2.0.3                   --  
 rem -- MAXIMUM_ORACLE_VERSION: 11.x.x.x                   --  
 rem -- PLATFORM_IDENTIFICATION: Generic                   --  
 rem --                                   --  
 rem -- IDENTIFICATION: inst_api.sql                     --  
 rem -- DESCRIPTION: This script installs CTX API into specified existin   --  
 rem --       schema. Must be run as SYS user with SYSDBA priv.    --  
 rem --       This script are interactive. Command-line version is  --  
 rem --       inst_parser_c.sql                    --  
 rem --                                   --  
 rem -- INTERNAL_FILE_VERSION: 0.0.0.3                    --  
 rem --                                   --  
 rem -- COPYRIGHT: Yuri Voinov (C) 2004, 2016                --  
 rem --                                   --  
 rem -- MODIFICATIONS:                            --  
 rem -- 23.05.2016 -Update copyright.                    --  
 rem -- 19.12.2006 -Add Oracle Text installation check.           --  
 rem -- 22.10.2006 -Fixed minor bug with GRANT EXECUTE ON ctx_thes package. --  
 rem --       Check grant if not already granted added.        --  
 rem -- 20.08.2006 -Initial code written.                  --  
 rem --------------------------------------------------------------------------  
   
 set verify off  
   
 whenever sqlerror exit sql.sqlcode  
   
 spool inst_api.log  
   
 prompt ============================================  
 prompt CTX_API installation ...  
 prompt ============================================  
 prompt --------------------------------------------  
   
 prompt Specify target user schema to install API  
 prompt  
 prompt You must specify sys password and ORACLE SID  
 prompt for database to run this script  
   
 prompt --------------------------------------------  
   
 accept schema_name char prompt 'Input target user schema:'  
   
 accept ora_sid char prompt 'Input ORACLE SID:'  
   
 accept sys_password char prompt 'Input SYS password:' hide  
   
 connect sys/&&sys_password@&&ora_sid as sysdba  
   
 set serveroutput on  
   
 declare  
  -- Check Oracle Text installed  
  v_ctx varchar2(30);  
 begin  
  select object_name  
  into v_ctx  
  from all_objects   
  where object_name = 'CTX_THES'  
   and object_type = 'PACKAGE';  
  dbms_output.put_line('Oracle Text Installed. Check OK.');  
 exception  
  when no_data_found then  
  raise_application_error(-20111,'Oracle Text does not installed.');  
 end;  
 /  
   
 set serveroutput off  
   
 declare  
  --  
  -- Check user and select target schema stuff.  
  --  
  c_ctx_role constant varchar2(30) := 'ctxapp'; -- CTXAPP role name  
  v_user varchar2(30); -- Target user buffer  
  v_ddl varchar2(255); -- DDL buffer  
  v_priv varchar2(40); -- Priv buffer  
 begin  
  begin  
  select username -- Check target user exists  
  into v_user  
  from all_users  
  where username = upper('&&schema_name');  
  exception  
  when too_many_rows then -- Check most suggest username  
   select max(username)  
   into v_user  
   from all_users  
   where username = upper('&&schema_name');  
  end;  
  --  
  -- Make grants  
  --  
  begin  
  -- Verify if grant is not already for target user  
  select privilege  
  into v_priv  
  from all_tab_privs  
  where grantee = upper('&&schema_name')  
   and privilege = 'EXECUTE'   
   and table_name = 'CTX_THES'  
   and grantor = 'CTXSYS';  
  exception  
  -- If not granted, then do it now  
  when no_data_found then  
   v_ddl := 'grant execute on ctxsys.ctx_thes to &&schema_name';  
   execute immediate(v_ddl);  
  end;  
  --  
  -- Grant CTXAPP role to target user  
  v_ddl := 'grant '||c_ctx_role||' to &&schema_name';  
  execute immediate(v_ddl);  
  -- Grant select on thes view to target user  
  v_ddl := 'grant select on ctxsys.ctx_thesauri to &&schema_name';  
  execute immediate(v_ddl);  
  -- Grant select on phrases view to target user  
  v_ddl := 'grant select on ctxsys.ctx_thes_phrases to &&schema_name';  
  execute immediate(v_ddl);  
  --  
  -- Set schema for installation  
  --  
  v_ddl := 'alter session set current_schema = '||v_user||'';  
  execute immediate(v_ddl);  
 exception  
  -- Exceptions handlers  
  when no_data_found then raise_application_error(-20110,'Target user specified NOT FOUND!');  
  when others then raise_application_error(-20120,'Error ORA'||SQLCODE);  
 end;  
 /  
   
 prompt  
 prompt CTX_API package specification loading...  
   
 @@ctx_api.sql  
   
 prompt  
 prompt CTX_API package body loading...  
   
@@prvtctxapi.pls  
   
rem @@prvtctxapi.plb  
   
 prompt ============================================  
 prompt CTX_API installation done.   
 prompt ============================================  
   
 spool off  
   
 disconnect  
   
 exit    

Пользуйтесь на здоровье. Лицензия на все это - BSD:

 -- COPYRIGHT: Yuri Voinov (C) 2004, 2016                   --  
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

PS. В третьей части статьи будут выложена документация по пакету, а в четвертой части будут рассмотрены вопросы построения и использования тезаурусов, реализация их поддержки в Oracle Text, реализация в виде WSDL-сервиса и другие смежные вопросы.