рдПрдХ jabber рд╕рд░реНрд╡рд░ рдХреЗ рд░реВрдк рдореЗрдВ YATE



YATE рдХрдИ рдорд╛рдпрдиреЛрдВ рдореЗрдВ рдПрдХ рдЕрдиреВрдард╛ рдЯреЗрд▓реАрдлреЛрди рд╕рд░реНрд╡рд░ рд╣реИред рд╡рд╣ рдПрд╕рдЖрдИрдкреА-рдЯреА рдХреЛ рд╕рдордЭрддрд╛ рд╣реИ, рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рдПрдЪ 323-рдПрд╕рдЖрдИрдкреА рдХрдирд╡рд░реНрдЯрд░ рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ рдУрдХреЗрдПрд╕ -7 (рдПрд╕рдПрд╕ 7) рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдкрд░рд┐рд╡рд╛рд░ рдХрд╛ рднреА рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рдФрд░ рдпрд╣ рд╕рдм GPL рдХреЗ рддрд╣рдд рдЙрдкрд▓рдмреНрдз рд╣реИред рджреВрд╕рд░реА рдУрд░, рд╕рдорд╕реНрдпрд╛ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рдкреНрд░рд▓реЗрдЦрди рдХреА рдХрдореА рд╣реИред

рд▓реЗрдХрд┐рди рдореИрдВ рдЯреЗрд▓реАрдлреЛрдиреА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ред рдЦрд░рдЧреЛрд╢ рди рдХреЗрд╡рд▓ рдореВрд▓реНрдпрд╡рд╛рди рдлрд░ рд╣реИрдВ, рдмрд▓реНрдХрд┐ рдпреЗрдЯ рдПрдХ рдЬреИрдмрд░ рд╕рд░реНрд╡рд░ рдХреЗ рд░реВрдк рдореЗрдВ рднреА рдХрд╛рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрд╣ рдордЬрд╝реЗрджрд╛рд░ рд╣реИ рдХрд┐ рдпреЗрдЯ рдХреЛ xmpp.org/xmpp-software/servers рдкрд░ рд╕реВрдЪреАрдмрджреНрдз рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рд╣рд╛рд▓рд╛рдБрдХрд┐ 2010 рдореЗрдВ рдЗрд╕рдХреЗ рдкреАрдЫреЗ рдЬреИрдмрд░ рд╕рд░реНрд╡рд░ рдХрд╛ рд╕рдорд░реНрдерди рджрд┐рдЦрд╛рдИ рджрд┐рдпрд╛ред

рдпрд╣ рдХрд╣рдирд╛ рдореБрд╢реНрдХрд┐рд▓ рд╣реИ рдХрд┐ рдЖрдкрдХреЛ рдпреЗрдЬрд╝рдмрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреНрдпреЛрдВ рд╣реЛ рд╕рдХрддреА рд╣реИ рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рдЗрдЬрд╝рдмрд░реНрдб, рдУрдкрдирдлрд╝рд╛рдпрд░, рдкреНрд░реЛрд╕реЛрдбреА рдФрд░ рдЯрд┐рдЧреЗрдЬрд╝ рд╣реИред рдпрд╣ рдкреНрд░рд╢реНрди рд▓реЗрдЦ рдХреЗ рджрд╛рдпрд░реЗ рд╕реЗ рдкрд░реЗ рд╣реИред рдореИрдВ рдЖрдкрдХреЛ рдПрдХ рдЕрдиреНрдп рд╡рд┐рдХрд▓реНрдк рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдХрд░рд╛рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред

рдЗрд╕рд▓рд┐рдП рдХрдЯ рдХреЗ рддрд╣рдд рд╕реНрд╡рд╛рдЧрдд рд╣реИред (рд╕рд╛рд╡рдзрд╛рдиреА, рдмрд╣реБрдд рд╕рд╛рд░реЗ рдкрддреНрд░!) рдЙрд╕реА рд╕рдордп рдореИрдВ рдЖрдкрдХреЛ рдмрддрд╛рдКрдВрдЧрд╛ рдХрд┐ рд╕рдХреНрд░рд┐рдп рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЛ рдХреИрд╕реЗ рддреЗрдЬ рдХрд┐рдпрд╛ рдЬрд╛рдПред

рд╣рдо рдЗрд╕ рддрдереНрдп рд╕реЗ рдЖрдЧреЗ рдмрдврд╝реЗрдВрдЧреЗ рдХрд┐ рдкрд╛рдардХ рд╕реНрд╡рддрдВрддреНрд░ рд░реВрдк рд╕реЗ рд╕реНрдерд╛рдкрдирд╛ рдХреЗ рд╕рд╛рде рд╕рд╛рдордирд╛ рдХрд░реЗрдЧрд╛ред рдЪрд▓реЛ рд╕реЗрдЯрдЕрдк рдХрд░рддреЗ рд╣реИрдВред

рд╣рдо рдЬреИрдмрд░ рд╕рд░реНрд╡рд░ рдХреЗ рдореБрдЦреНрдп рдорд╛рдкрджрдВрдбреЛрдВ рдХреЛ рдЗрдВрдЧрд┐рдд рдХрд░рддреЗ рд╣реИрдВ: рд╕реЙрдХреЗрдЯреНрд╕ рдкрд░ рд╕реБрдирдиреЗ рд╡рд╛рд▓рд╛ рдбреЛрдореЗрди, рдкрд╛рдЪрди рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд░рджреНрдж рдХрд░реЗрдВ (AD рдореЗрдВ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЗ рд▓рд┐рдП рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рдЙрд╕рдХреЗ рдореВрд▓ рд░реВрдк рдореЗрдВ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИ)ред
рдЪреЗрддрд╛рд╡рдиреА! C2s_plainauthonly рд╡рд┐рдХрд▓реНрдк рдХреЗрд╡рд▓ svn рдореЗрдВ рдЙрдкрд▓рдмреНрдз рд╣реИ, рд╕рдВрд╕реНрдХрд░рдг 4.3 рдореЗрдВ рдпрд╣ рдирд╣реАрдВ рд╣реИред

jabberserver.conf
[general] domains=mydomain.org c2s_plainauthonly=yes; force text password for LDAP auth [listener s2s] enable=yes type=s2s port=5269 [listener c2s] enable=yes type=c2s port=5222 


рд╣рдорд╛рд░реЗ рдкрд╛рд╕рд╡рд░реНрдб рдХреЛ рд╕реНрдкрд╖реНрдЯ рдкрд╛рда рдореЗрдВ рдкреНрд░рд╕рд╛рд░рд┐рдд рдирд╣реАрдВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ SSL рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдХреЛ рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЙрди рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ рдЗрдВрдЧрд┐рдд рдХрд░реЗрдВрдЧреЗ рдЬрд┐рдирдХреЗ рд▓рд┐рдП ssl рдкреНрд░рдорд╛рдгрдкрддреНрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рдзреНрдпрд╛рди рджрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐ рдЖрдкрдХреЛ sslcontext = рд╡рд┐рдХрд▓реНрдк рдХреЛ рд╕рдХреНрд╖рдо рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рдЬрд┐рд╕реЗ рдЖрдк рдбрд┐рдлрд╝реЙрд▓реНрдЯ рдХреЙрдиреНрдлрд┐рдЧрд░ рдореЗрдВ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕ рд╕реНрдерд┐рддрд┐ рдореЗрдВ Yate рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реЗ рдПрдХ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рдХрдиреЗрдХреНрд╢рди рдХреА рдЙрдореНрдореАрдж рдХрд░реЗрдЧрд╛, рдЬрдмрдХрд┐ рдЬреИрдмрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдЖрдорддреМрд░ рдкрд░ рджреЛ-рдЪрд░рдг рдХреА рд╕реНрдЯрд╛рд░реНрдЯрдЕрдк рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред

openssl.conf
 [yate] certificate=yate.pem domains=mydomain.org 

рдкреНрд░рдорд╛рдг рдкрддреНрд░ рдХреИрд╕реЗ рдЙрддреНрдкрдиреНрди рд╣реЛрддрд╛ рд╣реИ, рдореИрдВ рдирд╣реАрдВ рдмрддрд╛рдКрдВрдЧрд╛ред

рд╣рдо рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд▓рд┐рдП рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдФрд░ рдкрдВрдЬреАрдХрд░рдг рдореЙрдбреНрдпреВрд▓ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рддреЗ рд╣реИрдВред

register.conf
 [general] user.auth=yes user.register=yes user.unregister=yes engine.timer=yes [default] account=yate [user.auth] query=SELECT password FROM users WHERE username='${username}' AND password IS NOT NULL AND password<>'' result=password [user.register] query=UPDATE users SET location='${data}', expires=CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second WHERE username='${username}' [user.unregister] query=UPDATE users SET location=NULL,expires=NULL WHERE expires IS NOT NULL AND username='${username}' [engine.timer] query=UPDATE users SET location=NULL,expires=NULL WHERE expires IS NOT NULL AND expires<=CURRENT_TIMESTAMP 


MySQL рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛рдмреЗрд╕ рд╕рдВрд░рдЪрдирд╛ред рдЖрдк Postgres рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

 CREATE TABLE `offlinechat` ( `username` varchar(100) DEFAULT NULL, `xml` text, `time` int(11) NOT NULL, KEY `username` (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE `roster` ( `username` varchar(100) DEFAULT NULL, `contact` varchar(100) DEFAULT NULL, `name` varchar(100) DEFAULT NULL, `groups` varchar(100) DEFAULT NULL, `subscription` varchar(100) DEFAULT NULL, UNIQUE KEY `uc` (`username`,`contact`), KEY `username` (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE `users` ( `username` varchar(100) NOT NULL DEFAULT '', `password` varchar(100) DEFAULT NULL, `vcard` text, `location` varchar(100) DEFAULT NULL, `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 


рдбреЗрдЯрд╛рдмреЗрд╕ рдХрдиреЗрдХреНрдЯрд░ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ

mysqldb.conf
 [yate] database=yate user=yate password=yatepass 


Vcard рдлрд╝рдВрдХреНрд╢рди рдФрд░ рдСрдлрд╝рд▓рд╛рдЗрди рд╕рдВрджреЗрд╢ рд╕реЗрдЯ рдХрд░реЗрдВред

jbfeatures.conf
 [general] account=yate [vcard] get=SELECT vcard FROM users WHERE username='${username}' set=UPDATE users SET vcard='${vcard}' WHERE username='${username}' [offline_chat] get=SELECT * FROM offlinechat WHERE username='${username}' ORDER BY time add=INSERT INTO offlinechat (username,xml,time) VALUES ('${username}', '${xml}', ${time}) clear_user=DELETE FROM offlinechat WHERE username='${username}' 


рд░реЛрд╕реНрдЯрд░ рдХреЛ рдХрд╕реНрдЯрдорд╛рдЗрдЬрд╝ рдХрд░реЗрдВред

subscription.conf
 [general] account=yate user_roster_load=SELECT users.username, roster.* FROM users LEFT OUTER JOIN roster ON users.username=roster.username WHERE users.username='${username}' user_roster_delete=DELETE FROM roster WHERE username='${username}' contact_load=SELECT * FROM roster WHERE username='${username}' AND contact='${contact}' contact_subscription_set=INSERT roster (username,contact,subscription) VALUES ('${username}','${contact}','${subscription}') ON DUPLICATE KEY UPDATE subscription='${subscription}' contact_set=INSERT roster (username,contact,name,groups) VALUES ('${username}','${contact}','${name}','${groups}') ON DUPLICATE KEY UPDATE name='${name}',groups='${groups}' contact_set_full=INSERT roster (username,contact,name,groups,subscription) VALUES ('${username}','${contact}','${name}','${groups}','${subscription}') ON DUPLICATE KEY UPDATE name='${name}',groups='${groups}',subscription='${subscription}' contact_delete=DELETE FROM roster WHERE username='${username}' AND contact='${contact}' 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдЕрдзрд┐рдХрд╛рдВрд╢ рдЯреНрдпреВрдирд┐рдВрдЧ SQL рдХреНрд╡реЗрд░реА рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдиреАрдЪреЗ рдЖрддреА рд╣реИред рдпрд╣ рдЕрд╕рд╣рдЬ рд▓рдЧ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рд▓рдЪреАрд▓рд╛рдкрди рджреЗрддрд╛ рд╣реИред

рдЕрдм рд╕рдХреНрд░рд┐рдп рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЗ рдмрд╛рд░реЗ рдореЗрдВред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо register.conf рдХреЛ рдареАрдХ рдХрд░реЗрдВрдЧреЗ рддрд╛рдХрд┐ рд╕рдлрд▓ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЗ рдмрд╛рдж рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдЬреБрдбрд╝ рдЬрд╛рдПрдВ (рдФрд░ рдпрд╣рд╛рдВ рд▓рдЪреАрд▓рд╛рдкрди рдХрд╛рдо рдЖрдпрд╛!)ред
 [user.register] query=INSERT users (username,location,expires) VALUES ('${username}','${data}',CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second) ON DUPLICATE KEY UPDATE location='${data}', expires=CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second 

рджреВрд╕рд░реЗ, рдХреБрдЫ рдпреЗрдЯ рдЬрд╛рджреВ рдЬреЛрдбрд╝реЗрдВред рд╕рд░реНрд╡рд░ рдХреЛрд░ рдПрдХ рд╕рд┐рд╕реНрдЯрдо рд╕рдВрджреЗрд╢ рдкреНрд░рдмрдВрдзрдХ рд╣реИ, рдЬреЛ рдЗрдВрдЬрди рдорд╛рдирдХ рдФрд░ рдмрд╛рд╣рд░реА рдореЙрдбреНрдпреВрд▓ рдХреЗ рд╕рд╛рде рдЖрджрд╛рди-рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ (рдХреЛрдИ рдореМрд▓рд┐рдХ рдЕрдВрддрд░ рдирд╣реАрдВ рд╣реИрдВ)ред PHP рдореЗрдВ рдмрд╛рд╣рд░реА рдореЙрдбреНрдпреВрд▓ рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдмрд╕ рдЗрд╕реЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдореЗрдВ рд▓рд┐рдЦреЗрдВред рд╣рдорд╛рд░реА jabber.php рд╕реНрдХреНрд░рд┐рдкреНрдЯ user.auth рд╕рд┐рд╕реНрдЯрдо рд╕рдВрджреЗрд╢ (рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз) рдХреЛ рд░реЛрдХ рджреЗрдЧреА рдФрд░ AD рддрдХ рдкрд╣реБрдБрдЪ рдХрд░ рдЗрд╕реЗ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░реЗрдЧреАред рд╣реИрдВрдбрд▓рд░ = 40 рдХреА рдкреНрд░рд╛рдердорд┐рдХрддрд╛ рдХреЗ рдХрд╛рд░рдг рд╕рдВрджреЗрд╢ рдХреЛ рдЗрдВрдЯрд░рд╕реЗрдкреНрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдЬрдмрдХрд┐ рдмрд╛рдХреА рдореЙрдбреНрдпреВрд▓ рдореЗрдВ 50 рдпрд╛ рдЙрд╕рд╕реЗ рдЕрдзрд┐рдХ рдХреА рдкреНрд░рд╛рдердорд┐рдХрддрд╛ рд╣реИ, рдЬреЛ рдХрдо рдкреНрд░рд╛рдердорд┐рдХрддрд╛ рд╣реИред
extmodule.conf
 [general] scripts_dir=/etc/yate/ [scripts] jabber.php= 


рдФрд░ рдЕрдВрдд рдореЗрдВ, рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╕реНрдХреНрд░рд┐рдкреНрдЯ (рдореЙрдбреНрдпреВрд▓) рд╣реАред рдХреГрдкрдпрд╛ рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рдпрд╣ рдпреЗрдЯ рд╡рд┐рддрд░рдг рдХреЗ рд╕рд╛рде рд╢рд╛рдорд┐рд▓ libyate.php рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рдпрд╣ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рд▓рд┐рдП рд╕реБрд▓рдн рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдЗрд╕рд▓рд┐рдП рдЗрд╕реЗ рдЙрд╕реА рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рдХреЙрдкреА рдХрд░рдирд╛ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╣реИред рдпрджрд┐ рдбреЛрдореЗрди рдирд┐рдпрдВрддреНрд░рдХ рд╕реНрд╡-рд╣рд╕реНрддрд╛рдХреНрд╖рд░рд┐рдд рдкреНрд░рдорд╛рдг рдкрддреНрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рддреЛ /etc/openldap/ldap.conf рдореЗрдВ рд▓рд╛рдЗрди рдЬреЛрдбрд╝реЗрдВ
 TLS_REQCERT never 


jabber.php
 #!/usr/bin/php -q <?php require_once("libyate.php"); $ad_host = 'ldaps://dc.mydomain.org'; $ad_domain = 'mydomain.org'; /* Always the first action to do */ Yate::Init(); /* Install a handler for the call routing message */ Yate::Install("user.auth",40); function ad_auth($user, $password) { global $ad_host, $ad_domain; $con = ldap_connect($ad_host); return ldap_bind($con, "$user@$ad_domain", $password) & true; } /* The main loop. We pick events and handle them */ for (;;) { $ev=Yate::GetEvent(); /* If Yate disconnected us then exit cleanly */ if ($ev === false) break; /* Empty events are normal in non-blocking operation. This is an opportunity to do idle tasks and check timers */ if ($ev === true) { // Yate::Output("PHP event: empty"); continue; } /* If we reached here we should have a valid object */ switch ($ev->type) { case "incoming": switch ($ev->name) { case "user.auth": if (!isset($ev->params["digest-uri"])) { $username = $ev->params["username"]; $username = substr($username,0,strpos($username,'@')); $password = isset($ev->params["response"]) ? $ev->params["response"] : $ev->params["password"]; $auth = ad_auth($username, $password); if ($auth) { $ev->retval = $password; $ev->handled = true; } } break; } $ev->Acknowledge(); break; case "installed": Yate::Output("PHP Installed: " . $ev->name); break; case "uninstalled": Yate::Output("PHP Uninstalled: " . $ev->name); break; default: Yate::Output("PHP Event: " . $ev->type); } } Yate::Output("PHP: bye!"); /* vi: set ts=8 sw=4 sts=4 noet: */ ?> 

рдкреЙрд▓ рдЪрд┐рдЯреНрд╕рдХреБ рдХреЗ рдЕрдиреБрд╕рд╛рд░, рдпреЗрдЯ рдХреЗ рдкреНрд░рдореБрдЦ рдбреЗрд╡рд▓рдкрд░, рддреИрдпрд╛рд░ рд╣реЛрдиреЗ рдЪрд╛рд╣рд┐рдПред

рдФрд░ рдЕрдВрдд рдореЗрдВ, рдореЗрд░реЗ рдкрд╛рд╕ рджреЛ рд╕рдорд╛рдЪрд╛рд░ рд╣реИрдВ, рдЕрдЪреНрдЫрд╛ рдФрд░ рдмреБрд░рд╛:


рдпрджрд┐ рдХреБрдЫ рд╕реНрдкрд╖реНрдЯ рдирд╣реАрдВ рд╣реИ, рдпрд╛ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рддреЛ рд╕рд╡рд╛рд▓ рдкреВрдЫреЗрдВ, рдореИрдВ рдЬрд╡рд╛рдм рджреЗрдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реВрдВрдЧрд╛ред

Source: https://habr.com/ru/post/In166235/


All Articles