Пишемо Jabber-клієнт на Delphi, Різне, Програмування, статті

Починаємо писати Jabber-клієнт на Delphi. Jabber – система для швидкого обміну повідомленнями та інформацією про присутність (в контакт-листі) між будь-якими двома користувачами Інтернету на основі відкритого протоколу XMPP.


На відміну від тієї ж Аськи Jabber-мережу має на мій погляд більш розвинені можливості, а наявність розширень протоколу відкриває горизонти функціональності на недосяжні для комерційних IM-мереж, ось деякі з них:


Відкритість: протокол Jabber відкритий, загальнодоступний і досить легкий для розуміння; існує безліч реалізацій серверів і клієнтів, а також бібліотек з відкритим вихідним кодом.


Розширюваність: за допомогою просторів імен в XML можна розширити протокол Jabber для виконання необхідних завдань і для забезпечення підтримки взаємодії між різними системами. Загальні розширення розробляються під контролем Jabber Software Foundation.


Децентралізованість: хто завгодно може запустити свій власний сервер Jabber, що дозволяє організаціям і приватним особам займатися будь-якими експериментами з IM.


Безпека: будь-який сервер Jabber може бути ізольований від загальнодоступної мережі Jabber, багато з варіантів реалізації сервера використовують SSL при обміні між клієнтом і сервером, і немало [джерело не вказано 39 днів] клієнтів підтримують шифрування за допомогою PGP / GPG всередині протоколу.


Jabber задовольняє багато потреб приватних осіб і організацій. Але важливо розуміти, що він не є універсальним рішенням всіх завдань. Зокрема, Jabber не є:


Універсальним чат-клієнтом для різних систем IM – незважаючи на безліч клієнтів Jabber під різні платформи, вони не надають таких можливостей по взаємодії з різними системами IM, які забезпечуються програмами Miranda IM, Trillian або Pidgin: замість цього взаємодія між Jabber та іншими системами здійснюють шлюзи, розташовані на стороні сервера.


Універсальним рішенням проблем взаємодії з різними IM-системами – деякі сервера Jabber надають можливість спілкуватися з іншими системами IM через шлюзи, які транслюють протокол Jabber в протокол цих систем, а проте тільки від самих систем залежить здійснення взаємодії (до чого вони часом не прагнуть, і навіть навпаки).



Основні відомості про протокол XMPP


В основі протоколу XMPP (eXtensible Messaging and Presence Protocol) лежить мова XML. XMPP є відкритим, вільним протоколом для миттєвого обміну повідомленнями та інформацією про присутність в режимі околореального часу.


Спочатку спроектований легко розширюваним протокол крім передачі текстових повідомлень підтримує передачу голосу і файлів по мережі.


Даний протокол прийнятий як стандарт RFC.


Стандартний порт для Jabber-клієнтів – 5222.


Протокол регламентується наступними стандартами:


RFC 3920 – Extensible Messaging and Presence Protocol (XMPP): Core


RFC 3921 – Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence


Слід також зазначити, так як протокол є текстовим, а не бінарним відповідно у цього протоколу є слабкі сторони, а саме: надмірність переданої інформації, відсутність можливості передачі двійкових даних призводить до використання різних способів перекодування. В результаті цього, для передачі файлів доводиться використовувати додаткові протоколи, наприклад HTTP. Якщо цього не уникнути, то XMPP забезпечує вбудовану передачу файлів кодуючи інформацію використовуючи base64. Інша двійкова інформація, така як закодований розмова або графічні іконки включаються з використанням такого ж методу. Однак перш ніж рухатися далі розглянемо адресацію користувачів з Jabber-мережах.



Адресація користувачів в Jabber


Кожен користувач в мережі має унікальний ідентифікатор, адреса – Jabber ID (скорочено JID). Щоб уникнути необхідності існування сервера з повним списком всіх адрес, JID подібно адресою електронної пошти містить ім’я користувача (JID node) і DNS-адресу сервера (JID domain), на якому зареєстрований користувач, розділені знаком (@). Наприклад, користувач user, зареєстрований на сервері example.com, матиме наступну адресу (JID): user@example.com.


Також користувач може підключатися, перебуваючи в різних місцях, сервер дозволяє визначати додаткове значення, зване ресурсом, який ідентифікує клієнта користувача в даний момент. Так можна включити в адресу користувача (JID) ім’я його ресурсу (JID resource), додавши через слеш в кінці адреси.


Наприклад, нехай повну адресу користувача буде user@example.com / work, тоді повідомлення, надіслані на адресу user@example.com, дійдуть на вказану адресу незалежно від імені ресурсу, але повідомлення для user@example.com / work дійдуть на вказану адресу тільки при відповідному підключеному ресурсі.


Адреси (JID) можуть також використовуватися без явного зазначення імені користувача (із зазначенням імені ресурсу або без такого) для системних повідомлень і для контролю спеціальних можливостей на сервері.


Запам’ятаємо цю інформацію, вона нам знадобляться надалі.



Структура XML-пакетів Jabber протоколу (XML Streams)


Структура XML пакетів одержуваних з сервера і переданих на нього по специфікації RFC 3920 має наступний вигляд:










   /——————–/
/ <stream> /
/——————–/
/ <presence> /
/ <show/> /
/ </presence> /
/——————–/
/ <message to=”foo”> /
/ <body/> /
/ </message> /
/——————–/
/ <iq to=”bar”> /
/ <query/> /
/ </iq> /
/——————–/
/ … /
/——————–/
/ </stream> /
/——————–/


Як ви бачите, на схемі представлена ​​ієрархічна структура XML підрозділу на наступні елементи, так званий потік XML і елементи строф XML.


Потік XML – є контейнером для зберігання елементів строф XML. Потік XML починається з відкриття тега (з відповідними атрибутами і простором імен), кінець потоку XML закінчується закриттям тега . Під час обміну з сервером, клієнт і сам сервер може надсилати необмежену кількість елементів строф в потоці XML.


Строфи XML – це дискретні семантичні модулі представлені елементами, укладеними в потоці XML. Строфи XML є дочірніми елементами (child node) кореня XML . Початок будь-строфи XML позначено початком елемента (наприклад, ), кінець строфи XML позначений завершальним тегом (). У прикладі строфи XML: , , . Кожна строфа XML являє собою конкретну інформацію, так наприклад строфа представляє повідомлення, а інформаційний запит. Більш докладно строфи будуть розглянуті далі.


Примітка: незважаючи на стандарт, мною було помічено, що з деяких серверів можуть приходити пакети, просто містять строфи XML, але включені в потік XML.



Атрибути елементів XML


При приході XML у тегів можуть бути наступні основні атрибути:


to – кому (JID).


From – звідки (JID).


Id – унікальний ідентифікатор, так званий атрибут “системи виявлення атак”. Дозволяє конкретно ідентифікувати отримані дані. Рекомендовано робити його випадковим. Але в принципі це не обов’язково.


xml: lang – поточний мову, кодування даних.


Version – версія.


Теги можуть включати також і додаткові атрибути, які залежать від передавалась даних.


Приклад строфи з деякими атрибутами Ви можете побачити нижче:











<show/>
</presence>



Простору імен XML


Так як спочатку XMPP був задуманий, як протокол, що підтримує розширення, перед розробниками постало питання, як можна реалізувати дані розширення, не вносячи корективи в основний протокол. І рішення знайшлося. Це рішення – простір імен, досить відоме в XML.


Простір імен в XML – іменована сукупність імен елементів і атрибутів, що служить для забезпечення їх унікальності в XML-документі. Всі імена елементів в межах простору імен повинні бути унікальні. Таким чином, реалізується розрізнення однакових елементів XML або атрибутів. Для клієнтів Jabber зарезервовано простір імен “jabber: client”


Простору імен оголошуються за допомогою зарезервованого XML атрибута xmlns, значення якого є назвою простору імен.


Наприклад, елемент описаний простором імен “jabber: iq: roster” виглядає так:










<query xmlns=”jabber:iq:roster”>



Підготовка


Відразу обмовлюся, що я не ставлю перед собою завдання написати повноцінно працює клієнт відповідний повного стандарту XMPP. Занадто велика праця, скажімо так, однак основні методи роботи з XMPP будуть включені в мій вихідний компонент.


В якості основи для роботи клієнта мною були взяті напрацювання по роботі з WinSock Alex-а Demchenko, які він використовував в TICQClient, трохи перенені, подекуди змінені і додатково коментовані мною, для нашого демо-клієнта.


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


Що стосується наведених далі лістингів обміну протоколом, я постарався описати найцікавіші частини, якщо у вас подекуди виникнуть питання, докладніше ви можете дізнатися в RFC. Всі 800 основного RFC сторінок я не зможу Вам детально викласти, але критичні місця постараюся.


Також відразу обмовлюся, що наш приклад не буде підтримувати шифрування, то є дані будуть передаватися у відкритому вигляді. Зроблено це для спрощення розуміння прикладу. Вийшло, те, що вийшло, а добре иль погано вийшло судити Вам, шановні колеги.


Отже, для тестування нашого прикладу, мною був зареєстрований на сайті jabber.ru аккаунт delphi-test@jabber.ru з паролем delphi-test. Ці дані нам знадобляться для розбору протоколу обміну між сервером jabber (далі – Сервер) і нашим клієнтом (також – Клієнт) далі.



Проходження аутенфікаціі


Отже, першою дією при з’єднанні з сервером Jabber, яким повинен виконати наш клієнт – є аутенфікація. Аутенфікація відбуватиметься використовуючи механізм SASL аутенфікаціі, описаний в в “RFC 2831 – Using Digest Authentication as a SASL Mechanism “, алгоритм роботи який буде розглянутий детальніше, трохи далі.


Отже, ми встановили фізичне з’єднання з сервером, тепер нам потрібно пройти аутенфікацію, для цього клієнт посилає серверу наступний пакет:










<?xml version=”1.0″ encoding=”UTF-8″?>
<stream:stream to=”jabber.ru” xmlns=”jabber:client”
xmlns:stream=”http://etherx.jabber.org/streams” xml:l=”ru” version=”1.0″>


У відповідь сервер надішле підтвердження, про рукостисканні:










<?xml version=”1.0″?>
<stream:stream xmlns=”jabber:client”
xmlns:stream=”http://etherx.jabber.org/streams”
id=”3966489307″
from=”jabber.ru”
version=”1.0″
xml:lang=”en”>


Відразу ж після прийому першого пакету, прийде пакет, який містить інформацію про можливості і доступних механізмах сервера. Дані можливості потрібні, будуть для повноцінної роботи з сервером:










<stream:features><starttls xmlns=”urn:ietf:params:xml:ns:xmpp-tls”/>
<compression xmlns=”http://jabber.org/features/compress”>
<method>zlib</method>
</compression>
<mechanisms xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism></mechanisms>
<register xmlns=”http://jabber.org/features/iq-register”/>
</stream:features>


Що ми бачимо в пакеті, бачимо, що сервер підтримує zip компресію при передачі пакетів, підтримує механізм аутенфікаціі DIGEST-MD5, і інші можливості. Варто також відзначити, що можливості сервера залежать від самого сервера і залежно від програми можуть змінюватися. Докладніше ви можете дізнатися в RFC 3920. Проте нас цікавить те, що сервер підтримує механізм аутенфікаціі DIGEST-MD5. Відмінно, скажемо ми і відправимо йому пакет, що говорить про те, що ми хочемо пройти аутенфікацію використовуючи механізм DIGEST-MD5.










<auth xmlns=”urn:ietf:params:xml:ns:xmpp-sasl” mechanism=”DIGEST-MD5″/>


Після отримання даного пакету сервер надсилає нам, так званий challenge-пакет:










<challenge xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”>
bm9uY2U9IjIyNjQ3NzQ4Iixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz
</challenge>


Даний пакет ми повинні будемо розібрати. Як це зробити? Раніше уважний читач звернув увагу, що деякі пакети можуть передаватися в кодуванні Base64. Це наш випадок. Текстовий елемент містить інформацію в даному кодуванні, яка після розкодування прийме наступний вигляд:










nonce=”22647748″,qop=”auth”,charset=utf-8,algorithm=md5-sess


З цього рядка нам знадобиться значення Nonce для подальшого побудови відповіді серверу, після чого ми готуємо рядок відповіді, яку ми передамо на сервер у відповідному пакеті, попередньо закодований її в Base64. Отже, відповідна рядок матиме наступний вигляд:










username=”delphi-test”,
realm=”jabber.ru”,
nonce=”22647748″,
cnonce=”2313e069649daa0ca2b76363525059ebd”,
nc=00000001,
qop=auth,
digest-uri=”xmpp/jabber.ru”
,charset=utf-8,
response=16351f86cc5591312e20b4ccd880eadb


де:


username – JID-node користувача


realm – JID-domain користувача


nonce – Унікальний код сесії, присланий нам раніше сервером


cnonce – Унікальний код відповідної клієнтської сесії, згенерований клієнтом


nc – Так званий once count – скільки раз був використаний поточний nonce. Зазвичай значення параметра одно 00000001, його і будемо використовувати. Насправді параметр досить цікавий і стоїть окремого розгляду та вивчення в RFC, але як показала практика його сміливо можна ігнорувати.


digest-uri – Протокол підключення, для XMPP сервера він складається із з’єднання рядків “xmpp /” + JID Domain


charset – підтримка кодування пароля та імені, в нашому випадку UTF-8


І найважливіший параметр response в якому укладено ключ відповіді серверу, що включає в себе пароль та відповідні дані в форматі MD5 будується за певним алгоритмом.


Алгоритм побудови рядки відповіді і параметра Response більш докладно ми розглянемо далі в підрозділі “RFC 2831 використання MD5-Digest аутенфікаціі в SASL”. Поки приймемо до відома, що поточне і наступні дві дії відноситься вже до даного алгоритму.


Отже, рядок відповіді, ми сформували, закодували в Base64 і відправляємо назад серверу (все це має бути в одну строчку, але, щоб сторінка не розповзалася, розбито на декілька):










<response xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”>
dXNlcm5hbWU9ImRlbHBoaS10ZXN0IixyZWFsbT0iamFiYmVyLnJ1Iixub25jZT0iMjI2ND
c3NDgiLGNub25jZT0iMjMxM2UwNjk2NDlkYWEwY2EyYjc2MzYzNTI1MDU5ZWJkIixu
Yz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2phYmJlci5ydSIsY2
hhcnNldD11dGYtOCxyZXNwb25zZT0xNjM1MWY4NmNjNTU5MTMxMmUyMGI0Y2Nk
ODgwZWFkYg==
</response>


Якщо все нормально ми отримаємо таку відповідь:










<challenge xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”>
cnNwYXV0aD1lOTg5NjZjZjUxNjliZWUzOTYzNGU5Zjk5ZTIzZDZhYg==
</challenge>


Тут нам особливо нічого не потрібно, підтверджуємо прийняття його, відправивши з боку клієнта:










<response xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”/>


І якщо все пройшло успішно, то отримуємо з боку сервера пакет, що говорить нам про те, що аутенфікація пройшла успішно:










<success xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”/>


Далі ми знову надсилаємо пакет рукостискання:










<?xml version=”1.0″ encoding=”UTF-8″?>
<stream:stream to=”jabber.ru”
xmlns=”jabber:client”
xmlns:stream=”http://etherx.jabber.org/streams”
xml:l=”ru” version=”1.0″>


Отримуємо відповідь:










<?xml version=”1.0″?>
<stream:stream xmlns=”jabber:client”
xmlns:stream=”http://etherx.jabber.org/streams”
id=”4096919146″
from=”jabber.ru”
version=”1.0″
xml:lang=”en”>


Після чого за стандартом ми повинні зв’язати нашого клієнта з JID-ресурсом, що ми і робимо, посилаючи рядок у форматі UTF-8:










<iq type=”set” id=”bund_2″>
<bind xmlns=”urn:ietf:params:xml:ns:xmpp-bind”> тестова
</bind>
</iq>


Примітка: Всі листинги будуть представлені в ASCII форматі, хоча насправді прийом і посилка пакетів ведеться в UTF-8. Проте що б Вам не читати крякозабли в лістингах прикладів, кодування буде в показана в ASCII.


Сервер підтверджує зв’язування ресурсу з даним клієнтом:










<iq id=”bund_2″ type=”result”>
<bind xmlns=”urn:ietf:params:xml:ns:xmpp-bind”> delphi-test@jabber.ru / тестова
</bind>
</iq>


Клієнт посилає пакет присутності в мережі










<presence><show></show></presence>


і все, аутенфікація пройдена, значок клієнта в контакт-листі стає зелененьким, тепер він може посилати і приймати повідомлення.



RFC 2831 використання MD5-Digest аутенфікаціі в SASL


Отже, аутенфікація вирішує такі завдання: Передача пароля на сервер, в закритому вигляді, захист від повторюваних атак (monitoring nc value), захист (monitoring nonce) в певний проміжок часу від певного клієнта. Для того, щоб зрозуміти, як працює цей стандарт, розберемо основи SASL.


Загальні принципи роботи SASL


Метод SASL (Simple Authentication and Security Layer) використовується для додавання підтримки аутентифікації в різні протоколи з’єднання. Для аутентифікації можуть бути використані різні механізми.


Ім’я необхідного механізму задається клієнтом в команді аутентифікації. Якщо сервер підтримує зазначений механізм, він посилає клієнтові послідовність окликів (challenges), на які клієнт посилає відповіді (responses), щоб засвідчити себе. Вміст окликів і відповідей визначається використовуваним механізмом і може являти собою двійкові послідовності довільної довжини. Кодування послідовностей визначається прикладним протоколом. Замість чергового оклику сервер може послати підтвердження аутентифікації або відмову. Кодування також визначається протоколом. Замість відповіді клієнт може послати відмова від аутентифікації. Кодування знову визначається протоколом. В результаті обмінів відгуками і відповідями повинна відбутися аутентифікація (або відмову), передача ідентифікатора клієнта (порожній ідентифікатор тягне отримання ідентифікатора з аутентифікації) серверу і, можливо, домовленість про використання безпечного транспортного протоколу (security layer), включаючи максимальний розмір буфера шифрування.


Ідентифікатор клієнта може відрізнятися від ідентифікатора, який визначається з аутентифікації, для забезпечення можливості роботи проксі.


Реалізація на прикладі механізму MD5-Digest


Схема роботи SASL для нашого клієнта заснована на використанні механізму MD-Digest і має наступний алгоритм роботи:


Сервер посилає випадкову рядок nonce, наявність підтримки utf-8 в параметрі charset для імені та пароля, алгоритм аутентифікації (обов’язково md5-sess) в параметрі algorithm.


Тобто ті дані, що ми розкодовували раніше з пакета challenge:










nonce=”22647748″,qop=”auth”,charset=utf-8,algorithm=md5-sess


Клієнт відповідає рядком, що містить: ідентифікатор клієнта username, ідентифікатор домену realm, отриману від сервера випадкову рядок nonce, випадкову рядок клієнта cnonce, номер запиту (дозволяє серверу помітити спробу replay attack) nc. параметр digest-uri (поєднання імені сервісу, імені сервера тобто “xmpp /” + JID Domain), рядок responce підтверджує знання пароля і відповідь на оклик (MD5 від імені користувача, realm, пароля, випадкової рядка сервера, випадкової рядка клієнта, ідентифікатора клієнта, номера запиту, рівня захисту, digest-uri; деякі компоненти беруться у вигляді MD5, деякі в початковому вигляді, деякі в обох видах), використання utf-8 для імені та пароля, прийнятий алгоритм шифрування та ідентифікатор клієнта.


Тобто, як ви здогадалися ця та рядок, яку ми формуємо у відповідь:










username=”delphi-test”,
realm=”jabber.ru”,
nonce=”22647748″,
cnonce=”2313e069649daa0ca2b76363525059ebd”,
nc=00000001,
qop=auth,
digest-uri=”xmpp/jabber.ru”,
charset=utf-8,
response=16351f86cc5591312e20b4ccd880eadb


Сервер перевіряє відповідь на оклик і посилає відповідь на відповідь у схожому форматі (але все ж відрізняється, щоб клієнт міг переконатися в достовірності сервера). Даний механізм слабкіше системи з відкритими ключами, але краще простій CRAM-MD5.


Примітка: Варто відзначити, що може передбачатися спрощена протокол повторної аутентифікації (починається відразу з посилки клієнтом відповіді із збільшеним на 1 номером запиту).


Алгоритм обчислення рядки відповіді response


Алгоритм обчислення рядки відповіді response має наступну формулу:










response-value  =
HEX( KD ( HEX(H(A1)),
{ nonce-value, “:” nc-value, “:”,
cnonce-value, “:”, qop-value, “:”, HEX(H(A2)) }))
A1 = { H( { username-value, “:”, realm-value, “:”, passwd } ),
“:”, nonce-value, “:”, cnonce-value }
A2 = { “AUTHENTICATE:”, digest-uri-value }


Де:


Вираз {a, b, … } – Означає додавання рядків a, b


HEX (n) – 16-байтовий MD5-хеш n, наведений в 32 байтове Hex-рядок в нижньому регістрі. Фактично строкове представлення дайджесту MD5.


H (s) – 16-байтовий MD5-хеш рядка s


KD (k, s) – об’єднання даних (строк) k, s


H ({k, “:”, s}) – 16-байтовий MD5-хеш, отриманий в результаті складання рядки k, “:”, S


Як бачите, особливо нічого складного немає. Ось алгоритм розрахунку реалізований мною на Delphi:










function GenResponse(UserName, realm, digest_uri, Pass, nonce, cnonce : String) : string;
const
nc = “00000001”;
gop = “auth”;
var
A2, HA1, HA2,
sJID : String;
Razdel : Byte;
Context : TMD5Context;
DigestJID : TMD5Digest;
DigestHA1 : TMD5Digest;
DigestHA2 : TMD5Digest;
DigestResponse : TMD5Digest;
begin
Razdel := Ord(“:”); / / Обчислюємо А1 за формулою RFC 2831
// A1 = { H( { username-value, “:”, realm-value, “:”, passwd } ),
// “:”, nonce-value, “:”, cnonce-value, “:”, authzid-value }
sJID := format(“%S:%S:%S”, [username, realm, Pass]);
MD5Init(Context);
MD5UpdateBuffer(Context, PByteArray(@sJID[1]) , Length(sJID));
MD5Final(DigestJID, Context);
MD5Init(Context);
MD5UpdateBuffer(Context, PByteArray(@DigestJID),SizeOf(TMD5Digest));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@nonce[1]) , Length(nonce));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@cnonce[1]) , Length(cnonce));
MD5Final(DigestHA1, Context);
/ / Обчислюємо А2 за формулою RFC 2831
// A2 = { “AUTHENTICATE:”, digest-uri-value }
A2 := format(“AUTHENTICATE:%S”, [digest_uri]);
MD5Init(Context);
MD5UpdateBuffer(Context, PByteArray(@A2[1]) , Length(A2));
MD5Final(DigestHA2, Context);
/ / Обчислюємо RESPONSE за формулою RFC 2831
// HEX( KD ( HEX(H(A1)),
// { nonce-value, “:” nc-value, “:”,
// cnonce-value, “:”, qop-value, “:”, HEX(H(A2)) }))
HA1 := LowerCase( PacketToHex(@DigestHA1, SizeOf(TMD5Digest)));
HA2 := LowerCase( PacketToHex(@DigestHA2, SizeOf(TMD5Digest)));
MD5Init(Context);
MD5UpdateBuffer(Context, PByteArray(@HA1[1]),Length(HA1));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@nonce[1]) , Length(nonce));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@nc[1]) , Length(nc));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@cnonce[1]) , Length(cnonce));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@gop[1]) , Length(gop));
MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
MD5UpdateBuffer(Context, PByteArray(@HA2[1]),Length(HA2));
MD5Final(DigestResponse, Context);
Result := LowerCase( PacketToHex(@DigestResponse, SizeOf(TMD5Digest)) )
end;


На вході функція отримує параметри розглянуті нами раніше.



Базові семантичні модулі


Після того як ми пройшли авторизацію, розберемо основні базові семантичні модулі XML-строф реалізованих в протоколі, їх досить небагато:


– Презентаційні дані, визначають статусне стан, видимість користувачів і керування підпискою.


– Власне самі повідомлення передані або прийняті користувачем.


– Info Query, дані інформаційних запитів, включають в себе самі запити, а також результати виконання. Дані запити дозволяють Jabber-клієнтам обмінюється різними даними між собою. Інформаційне наповнення запиту і відповіді визначено в просторі імен дочірнього елемента. Додаткові розширення протоколу (XEP – XMPP Extension Protocol) дуже сильно використовують запити. Докладніше про них я розповім далі.



Відправка і прийом повідомлень


Прийом і відправлення повідомлень здійснюється через XML-строфу . Так як в Jabber-е передбачені різні типи повідомлень, то для їх розмежувань передбачений атрибут type містить інформацію про тип повідомлення.


Типи повідомлень можуть бути наступні:


chat – Одиночне повідомлення від клієнта до клієнта.


error – Повідомлення про помилку. Сталася помилка пов’язана з попереднім, посланим поодиноким повідомленням.


groupchat – Груповий чат. Дане повідомлення прийшло з групового чату, що діє за ознакою “Одне повідомлення – багатьом одержувачам”.


headline – Системне повідомлення, автоматично генерується різними сервісами для шіровещательной розсилки (новини, спорт, RSS-канали тощо) Відповідати на такі повідомлення не потрібно, та й не навіщо.


normal – одиночне повідомлення, що посилається поза контекстом взаємно-однозначної сеансу зв’язку або групового чату. Тобто це таке повідомлення, на яке користувач може дати відповідь, не враховуючи хронологією сеансу зв’язку.



Дочірні елементи


У XML-строфи можуть бути наступні дочірні елементи, визначені простором імен “jabber: client” або “jabber: server”. За замовчуванням за цими елементами зарезервовано простір імен “Jabber: client”. Якщо має тип error то обробка такого повідомлення йде відповідно до RFC 3920 XMPP-Core.


Елемент строфи може містити будь-який з наступних дочірніх елементів без явного оголошення простору імен:


<subject/>, <body/>, <thread/>.


Де елемент – є тілом повідомлення. У значенні елемента міститься сам текст повідомлення, попередньо кодуванні через обмеження XML. Елемент може містити атрибут “xml: lang” містить мову повідомлення. В можуть бути включені множинні екземпляри елементів , але тільки за умови, що кожен екземпляр має атрибутом “xml: lang” з відмінним мовним значенням.


Наприклад:










  Текст (тіло) повідомлення 
</message>


Елемент визначає тему повідомлення. На нього діють ті-ж правила, що і для . Множинні екземпляри можуть бути включені для розширення та доповнення суміжних тим, але за умови, що кожен екземпляр має атрибутом “xml: lang” з відмінним мовним значенням.


Елемент cлужіт для забезпечення хронології сеансу. Значення елемента згенерованого відправником повинно бути надіслане тому в будь-яких відповідях. Цей елемент є додатковим і зазвичай не використовується для обміну повідомленнями між користувачами. Використовується він в сеансах зв’язку. Більш докладно можна прочитати про нього в RFC 3921.



Перекодування символів тексту


Оскільки символи “<" і ">” використовуються для позначення самих XML тегів, то їх вставка в текст повідомлення неприпустима (за винятком випадку, коли вставлений символ “>”, але ніякої тег не був відкритий). Тому для коректного формування XML наступні символи повинні бути замінені в тілі повідомлення при відправці оного і відповідно назад повернені при прийомі:


“<" В "<"


“>” В “>”.


Таким чином, щоб написати “2> 1”, потрібно написати “2> 1”. Те ж саме стосується і знака “&” – він замінюється “&”. Також рекомендується замінювати і лапки (хоча в більшості випадків вони добре розпізнаються і без цього). Еквівалент подвійних лапок – “” “



Статуси, стану, інформація про присутність, управління підпискою


Прийом і відправлення статусних повідомлень, а також інформації про видимість контактів і підписки на повідомлення від них, здійснюється через XML-строфу .


Атрибут type строфи є додатковим.


Строфа, яка не володіє атрибутом type, використовується Jabber-ом, для повідомлень про присутність контакту в мережі Jabber і вказує на те, що даний контакт перебуває в мережі (онлайні) і доступний для комунікації.


Якщо атрибут type прісутствет в строфі , то він управляє підпискою на повідомлення і зміну статусів іншого контакту (об’єкта). Аналог підписки в IM-мережах є проходження авторизації в ICQ.


Якщо атрибут включений, то він повинен містити мати одне з наступних значень:,


unavailable – Сигналізує, що даний контакт, більше не доступний для комунікацій. Фактично контакт вийшов в оффлайн.


subscribe – Запит на підписку (авторизацію) від іншого контакту.


subscribed – Інформує про те, що контакт дозволив авторизацію.


unsubscribe – Відправник анулює підписку.


unsubscribed – Запит на анулювання передплати (відкликання авторизації) від іншого контакту.


probe – Запит про поточний присутності контакту тільки сервером від імені користувача.


error – Помилка, яка сталася при доставки попередніх даних. Обробка такого повідомлення йде відповідно до RFC 3920 XMPP-Core.


Наприклад, запит на підписку від контакту ivanov@jabber.ru для нашого контакту може виглядати так:










<presence to=”delphi-test@jabber.ru” type=”subscribe”
from=”ivanov@jabber.ru”/>
</presence>


Дозвіл авторизації у відповідь:










<presence to=”ivanov@jabber.ru” type=”subscribed”/>


А заборона (відмову) ось так:










<presence to=”ivanov@jabber.ru” type=”unsubscribed”/>



Дочірні елементи


XML-строфа може містити такі дочірні елементи, визначені простором імен “jabber: client” або “jabber: server”: , , . За замовчуванням за цими елементами зарезервовано простір імен “jabber: client”. Якщо має тип error то обробка такого повідомлення йде відповідно до RFC 3920 XMPP-Core.


Елемент визначає статус контакту і може мати наступні значення:


away – Відійшов,


chat – Готовий чатиться (В мережі),


dnd – Зайнятий,


xa – Недоступний


Порожній елемент визначає статус контакту “У мережі”.


Елемент визначає статусне повідомлення. Значенням елемента є рядок з текстом повідомлення, наприклад:










<presence>  Дивлюся фільм 
</presence>


Необов’язковий елемент визначає пріоритет рівня ресурсу. Значенням елемента є число від -128 до 127.



Інформаційні запити


Інформаційні запити поділяються на стандартні, визначені простором імен “jabber: client” або “jabber: server”, що забезпечують базові функціональні можливості і розширені.


Розширені запити, визначені додатковими просторами імен, описані в різних доповненнях до протоколу XMPP.


Простір імен розширених запитів, може містити будь-яке значення, крім зарезервованих наступних просторів імен: “jabber: client”, “jabber: server” або “http://etherx.jabber.org/streams”. Таке розширення дозволяє надати протоколу XMPP додаткову функціональність і гнучкість. Таким чином, розширений інформаційний запит може містити один або більше додаткових дочірніх елементів, які визначають інформаційне наповнення, яке розширює значення даного запиту. Це кардинальна відмінність від стандартного інформаційного запиту.


Стандартний запит не може містити, дочірні елементи, крім елементу . Наявність цього елемента в запиті показує наявність помилки.


Підтримка будь-якого розширеного простору імен є додатковою можливістю з боку клієнта. Якщо клієнт не розуміє такий простір імен, тобто фактично не підтримує дане розширення, то він повинен проігнорувати цей пакет. Більш докладно ви можете прочитати про це в RFC 3921.


Структурна схема обміну інформаційними запитами:










 Запит Відповідь
———- ———-
/ /
/ <iq type=”get” id=”1″> /
/ ————————> /
/ /
/ <iq type=”result” id=”1″> /
/ <———————— /
/ /
/ <iq type=”set” id=”2″> /
/ ————————> /
/ /
/ <iq type=”error” id=”2″> /
/ <———————— /
/ /


Як ми бачимо, на структурній схемі обмін між клієнтами відбувається за таким алгоритмом. Запитуюча сторона посилає запит з атрибутом type рівним значенням “get”. Даний атрибут на приймаючої стороні говорить клієнтові, що ви повинні надати інформацію по даному запиту (для розширених запитів за умови, що він підтримується клієнтом). Приймаюча сторона відправляє відповідь з атрибутом з атрибутом type рівним значенням “result”.


Це перший варіант обміну. Існує і другий, коли запитуюча сторона інформує приймаючу про якесь зміну, для цього вона відправляє запит з атрибутом type рівним значенням “set”. Дане значення атрибута говорить про те, що приймаюча сторона повинна обробити надіслані дані. Якщо приймаюча сторона не може з якихось причин обробити надіслані дані, то у відповідь вона посилає строфу З атрибутом type рівним значенням “error” інформуючи запитуючу сторону про неможливість обробки. Якщо приймаюча сторона коректно опрацювала запит c атрибутом “set” то вона повертає відповідь з атрибутом рівним значенням “result”.


Приклад розширеного запиту визначає інформацію про використаний клієнті (XEP-0092 Software Version):


Запит:










<iq from=”delphi-test2@jabber.ru/QIP”  to = “delphi-test@jabber.ru / тестова”
xml:lang=”ru” type=”get” id=”qip_30″>
<query xmlns=”jabber:iq:version”/>
</iq>


Відповідь:










<iq type=”result” to=”delphi-test2@jabber.ru/QIP”  from = “delphi-test@jabber.ru / тестова”
id=”qip_30″>
<query xmlns=”jabber:iq:version”> Мій клієнт
<version>0.5.0.1</version>
</query>
</iq>



Робота з ростер-листом (списком контактів)


Ростер-лист або аналог списку контактів в мережах ICQ в Jabber-е представлений списком, що містить JID-контакти у вигляді елементів XML зберігаються на сервері від імені користувача. Так як ростер-лист збережений сервером від імені користувача, то користувач може звернутися до інформації списку від будь-якого ресурсу.


Управління ростер-листом (списком) здійснюється через розширений інформаційний запит містить дочірній елемент c простором імен “jabber: iq: roster”. Елемент може містити один або більше дочірніх елементів містять інформацію про контакт.


Унікальний ідентифікатор кожного елемента списку – це JID Контанти, що формується в атрибуті jid Значення атрибута jid має форму user @ domain без вказівки ресурсу. Поточний стан підписки користувача (контакту) щодо елемента зафіксовано в атрибуті subscription і може приймати такі значення:


none – У користувача немає підписки до контакту, немає підписки і до інформації присутності користувача


to – у користувача є підписка до інформації присутності контакту, але у контакту немає підписки до інформації присутності користувача


from – контакт має підписка до інформації присутності користувача, але у користувача немає підписки до інформації присутності контакту


both – у користувача є підписка до присутності контакту, та й у контакт має підписка до користувача.



Запит списку контактів при вході в систему


При вході в систему клієнт Jabber повинен послати серверу інформаційний запит про отримання ростер-листа.


Запит ростер-листа клієнтом:











Отримання ростер-листа з сервера:










<iq from=”delphi-test@jabber.ru”  to = “delphi-test@jabber.ru / тестова” id = “roster_1″
type=”result”>
<query xmlns=”jabber:iq:roster”>
<item subscription=”from” name = “Тест 2”
jid=”delphi-test2@jabber.ru”/>
</query>
</iq>



Управління ростер-листом


Додавання або редагування контакту. При відсутності контакту в ростер-листі контакт буде додано, при наявності відредагований.


Додавання / коригування. Клієнт посилає наступний пакет.











<query xmlns=”jabber:iq:roster”> Група контакту
</item>
</query>
</iq>


Після додавання / оновлення інформації про контакт на сервері, сервер сповіщає всі доступні ресурси користувача про внесеної інформації. Оповіщення служить сигналом для синхронізації даних клієнтів про даному контакті з даними сервера.


Оповіщення сервера:











<query xmlns=”jabber:iq:roster”> Група контакту
</item>
</query>
</iq>


Інформація про результат:












Будь-коли, користувач видалити контакт з ростер-списку, для цього клієнт повинен надіслати запит з атрибутом subscription елемента рівним значенням “remove”:











<query xmlns=”jabber:iq:roster”>
</query>
</iq>


Як і у випадку з додаванням / коригуванням контакту сервер оповіщає клієнти про видаленні контакту. Вказівкою факту видалення служить атрибут subscription рівним значенням “remove” в елементі .











<query xmlns=”jabber:iq:roster”>
</query>
</iq>



Висновок


Як ви бачите, нічого особливо складного немає. Простий Jabber-клієнт з мінімальною функціональністю представлений в прикладі. Також в архів викладений парсер TjanXMLParser2, RFC 3920, 3921.


До статті додається приклад.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*