Compare commits

..

205 Commits

Author SHA1 Message Date
Mickaël Rémond e1eef9f84b * ejabberd 1.1.2 final
SVN Revision: 656
2006-09-27 20:47:56 +00:00
Mickaël Rémond 441d31ac3e * Last ejabberd 1.1.2 tag: Fixed release note.
SVN Revision: 654
2006-09-27 20:42:04 +00:00
Mickaël Rémond fcc4adcde5 * src/release_notes_1.1.2.txt: Fixed hanging paragraph
SVN Revision: 653
2006-09-27 20:41:02 +00:00
Mickaël Rémond 9057197446 * ejabberd-1.1.2
SVN Revision: 652
2006-09-27 17:38:50 +00:00
Mickaël Rémond 8515833954 * Added release note to documentation.
SVN Revision: 650
2006-09-27 17:29:45 +00:00
Mickaël Rémond 51fa955e11 * Tag for ejabberd 1.1.2 release.
SVN Revision: 649
2006-09-27 16:34:40 +00:00
Mickaël Rémond 7e948fbbac * doc/release_notes_1.1.2.txt: Fixed typos and rephrasing.
SVN Revision: 648
2006-09-27 16:22:09 +00:00
Mickaël Rémond 9bf5a83598 * doc/release_notes_1.1.2.txt: Fixed typos and rephrasing.
SVN Revision: 648
2006-09-27 16:22:09 +00:00
Mickaël Rémond de306d0364 * doc/release_notes_1.1.2.txt: Minor fixes.
SVN Revision: 647
2006-09-27 13:34:18 +00:00
Mickaël Rémond 616a273286 * doc/release_notes_1.1.2.txt: Minor fixes.
SVN Revision: 647
2006-09-27 13:34:18 +00:00
Mickaël Rémond 7214549508 * doc/guide.tex: Fixed Latexish typos.
SVN Revision: 646
2006-09-27 13:09:50 +00:00
Mickaël Rémond c0f898bbb8 * doc/guide.tex: Fixed Latexish typos.
SVN Revision: 646
2006-09-27 13:09:50 +00:00
Mickaël Rémond 4cb24c3e6a * doc/release_notes_1.1.2.txt: Draft release notes.
SVN Revision: 645
2006-09-26 17:17:31 +00:00
Mickaël Rémond 910d6667eb * doc/release_notes_1.1.2.txt: Draft release notes.
SVN Revision: 645
2006-09-26 17:17:31 +00:00
Mickaël Rémond 16c2d399e9 * src/msgs/pl.msg: Updated (thanks to Andrzej Smyk).
SVN Revision: 644
2006-09-26 14:07:35 +00:00
Mickaël Rémond fef7c626e9 * src/msgs/pl.msg: Updated (thanks to Andrzej Smyk).
SVN Revision: 644
2006-09-26 14:07:35 +00:00
Mickaël Rémond 54d05f64ba Improvement coming from trunk (SVN #633):
* src/ejabberd_s2s.erl: Added incoming-s2s-number and
outgoing-s2s-number ejabberdctl commands

SVN Revision: 643
2006-09-26 11:12:03 +00:00
Mickaël Rémond 30371b2483 * src/ejabberd_s2s.erl: More precise message for the new s2s
statistic command.

SVN Revision: 642
2006-09-26 11:08:44 +00:00
Mickaël Rémond 2a542e9e09 * src/mod_muc/mod_muc_room.erl: Minor english update.
* src/msgs/pl.msg: Likewise.
* src/msgs/uk.msg: Likewise.
* src/msgs/pt-br.msg: Likewise.
* src/msgs/cs.msg: Likewise.
* src/msgs/ru.msg: Likewise.
* src/msgs/es.msg: Likewise.
* src/msgs/fr.msg: Likewise.
* src/msgs/de.msg: Likewise.
* src/msgs/nl.msg: Likewise.

SVN Revision: 641
2006-09-26 10:48:05 +00:00
Mickaël Rémond 11da06b5e7 * src/mod_muc/mod_muc_room.erl: Minor english update.
* src/msgs/pl.msg: Likewise.
* src/msgs/uk.msg: Likewise.
* src/msgs/pt-br.msg: Likewise.
* src/msgs/cs.msg: Likewise.
* src/msgs/ru.msg: Likewise.
* src/msgs/es.msg: Likewise.
* src/msgs/fr.msg: Likewise.
* src/msgs/de.msg: Likewise.
* src/msgs/nl.msg: Likewise.

SVN Revision: 641
2006-09-26 10:48:05 +00:00
Mickaël Rémond d9487135c0 * src/msgs/pt-br.msg: Updated Brazilian translation (thanks to
Renato Botelho) and clean-up.

SVN Revision: 640
2006-09-26 10:31:53 +00:00
Mickaël Rémond 1451034393 * src/msgs/pt-br.msg: Updated Brazilian translation (thanks to
Renato Botelho) and clean-up.

SVN Revision: 640
2006-09-26 10:31:53 +00:00
Mickaël Rémond 539000bf62 * src/msgs/de.msg: Updated German translation (thanks to Nikolaus
Polak).

SVN Revision: 639
2006-09-25 22:06:37 +00:00
Mickaël Rémond d7e96b89ec * src/msgs/de.msg: Updated German translation (thanks to Nikolaus
Polak).

SVN Revision: 639
2006-09-25 22:06:37 +00:00
Mickaël Rémond 4646bf818a * src/ejabberd.app: Version update for ejabberd 1.1.2.
SVN Revision: 638
2006-09-25 20:53:55 +00:00
Mickaël Rémond f5750063f0 * src/ejabberd.app: Version update for ejabberd 1.1.2.
SVN Revision: 638
2006-09-25 20:53:55 +00:00
Mickaël Rémond 751fbec0b6 ChangeLog typo
SVN Revision: 637
2006-09-25 15:36:17 +00:00
Mickaël Rémond 2923c6a962 ChangeLog typo
SVN Revision: 637
2006-09-25 15:36:17 +00:00
Mickaël Rémond 37f56859e7 * src/msgs/pl.msg: Updated German translation (thanks to Zbyszek
Zolkiewski).

SVN Revision: 636
2006-09-25 15:19:09 +00:00
Mickaël Rémond 041004c042 * src/msgs/pl.msg: Updated German translation (thanks to Zbyszek
Zolkiewski).

SVN Revision: 636
2006-09-25 15:19:09 +00:00
Mickaël Rémond 9d7006dfa2 * src/msgs/de.msg: Updated German translation (thanks to Marvin
Preuss).

SVN Revision: 635
2006-09-25 14:41:33 +00:00
Mickaël Rémond 1c2195ebdb * src/msgs/de.msg: Updated German translation (thanks to Marvin
Preuss).

SVN Revision: 635
2006-09-25 14:41:33 +00:00
Mickaël Rémond b2acdac5fa * src/msgs/nl.msg: Updated Dutch translation (thanks to Sander
Devrieze).

SVN Revision: 634
2006-09-25 14:30:44 +00:00
Mickaël Rémond c11746181d * src/msgs/nl.msg: Updated Dutch translation (thanks to Sander
Devrieze).

SVN Revision: 634
2006-09-25 14:30:44 +00:00
Alexey Shchepin 3ab30f34fb * src/ejabberd_s2s.erl: Added incoming-s2s-number and
outgoing-s2s-number ejabberdctl commands

SVN Revision: 633
2006-09-25 13:36:41 +00:00
Mickaël Rémond 5d4093b8ac * src/msgs/cs.msg: Updated Czech translation and removed unused
strings.

SVN Revision: 632
2006-09-25 10:47:52 +00:00
Mickaël Rémond 3a3682a7b3 * src/msgs/cs.msg: Updated Czech translation and removed unused
strings.

SVN Revision: 632
2006-09-25 10:47:52 +00:00
Mickaël Rémond 7dbb35800d * doc/introduction.tex: Minor doc updates for release 1.1.2.
SVN Revision: 631
2006-09-25 08:46:56 +00:00
Mickaël Rémond 6a3afa778b * doc/introduction.tex: Minor doc updates for release 1.1.2.
SVN Revision: 630
2006-09-25 07:55:43 +00:00
Alexey Shchepin aa478100d8 * src/ejabberd_socket.erl: Support for non-xml sockets
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_service.erl: Likewise
* src/web/ejabberd_http.erl: Likewise

SVN Revision: 629
2006-09-25 03:51:11 +00:00
Mickaël Rémond d574757de4 * src/msgs/es.msg: Updated Spanish translation (thanks to Badlop).
SVN Revision: 628
2006-09-24 15:44:24 +00:00
Mickaël Rémond 9ababf2a1d * src/msgs/es.msg: Updated Spanish translation (thanks to Badlop).
SVN Revision: 628
2006-09-24 15:44:24 +00:00
Mickaël Rémond 5b9526d998 * src/eldap/eldap_filter: Forgot to add eldap_filter.erl in ejabberd-1.1.2 branch
SVN Revision: 627
2006-09-24 15:31:21 +00:00
Mickaël Rémond 519e78eed6 * src/mod_muc/mod_muc_room.erl: Strings update (thanks to
Serguei Golovan).
* src/msgs/ru.msg: Updated Russian translation (thanks to
Serguei Golovan).
* src/msgs/uk.msg: Updated Ukrainian translation (thanks to
Serguei Golovan).
* src/msgs/fr.msg: Update French translation.

SVN Revision: 626
2006-09-24 14:45:12 +00:00
Mickaël Rémond 79f5d2772e * src/mod_muc/mod_muc_room.erl: Strings update (thanks to
Serguei Golovan).
* src/msgs/ru.msg: Updated Russian translation (thanks to
Serguei Golovan).
* src/msgs/uk.msg: Updated Ukrainian translation (thanks to
Serguei Golovan).
* src/msgs/fr.msg: Update French translation.

SVN Revision: 626
2006-09-24 14:45:12 +00:00
Mickaël Rémond b2e83ffca6 * src/doc/guide.html: Minor W3C compliance fix in an Hevea
generated URL.

SVN Revision: 625
2006-09-24 12:35:47 +00:00
Mickaël Rémond 78aeb15aac * src/doc/guide.html: Minor W3C compliance fix in an Hevea
generated URL.

SVN Revision: 625
2006-09-24 12:35:47 +00:00
Mickaël Rémond 322fc0a6b8 * src/doc/features.html: Added to be consistent (guide.html is in the
repository to make Latex optional, but still allow access to the doc).
* src/ejabberd.hrl: Updated to version 1.1.2.

SVN Revision: 624
2006-09-24 09:39:04 +00:00
Mickaël Rémond d786782a0f * src/doc/features.html: Added to be consistent (guide.html is in the
repository to make Latex optional, but still allow access to the doc).
* src/ejabberd.hrl: Updated to version 1.1.2.

SVN Revision: 624
2006-09-24 09:39:04 +00:00
Mickaël Rémond ccfcfa49fa Improvement coming from trunk (SVN #622):
* src/eldap/eldap.erl: Enable the keepalive socket option (thanks
to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Now uses two LDAP connections
(thanks to Evgeniy Khramtsov)
* src/eldap/eldap_filter.erl: Bugfix (thanks to Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise

SVN Revision: 623
2006-09-23 09:52:53 +00:00
Mickaël Rémond ce3bdf52d9 * src/msgs/cs.msg: Added Czech translation (thanks to Milos Svasek).
SVN Revision: 622
2006-09-23 09:20:19 +00:00
Mickaël Rémond e734ad1682 * src/msgs/cs.msg: Added Czech translation (thanks to Milos Svasek).
SVN Revision: 622
2006-09-23 09:20:19 +00:00
Alexey Shchepin bb4f9f63b9 * src/eldap/eldap.erl: Enable the keepalive socket option (thanks
to Evgeniy Khramtsov)

* src/ejabberd_auth_ldap.erl: Now uses two LDAP connections
(thanks to Evgeniy Khramtsov)

* src/eldap/eldap_filter.erl: Bugfix (thanks to Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise

SVN Revision: 621
2006-09-23 04:28:03 +00:00
Mickaël Rémond 4ea5e493ea * src/mod_muc/mod_muc.erl: Component name is now more user
friendly (thanks to Badlop).
* src/mod_irc/mod_irc.erl: Likewise.
* src/mod_pubsub/mod_pubsub.erl: Likewise.

SVN Revision: 620
2006-09-22 17:01:16 +00:00
Mickaël Rémond 112c609222 * src/mod_muc/mod_muc.erl: Component name is now more user
friendly (thanks to Badlop).
* src/mod_irc/mod_irc.erl: Likewise.
* src/mod_pubsub/mod_pubsub.erl: Likewise.

SVN Revision: 620
2006-09-22 17:01:16 +00:00
Mickaël Rémond d31ab5ca13 * src/msgs/fr.msg: updated French translation.
SVN Revision: 619
2006-09-22 09:25:47 +00:00
Mickaël Rémond 33431d1a45 * src/msgs/fr.msg: updated French translation.
SVN Revision: 619
2006-09-22 09:25:47 +00:00
Mickaël Rémond 1632aec61e Improvement coming from trunk (SVN #617):
* doc/guide.tex: Large improvements for ejabberd 1.1.2 (thanks to Sander
Devrieze)
* doc/version.tex: Likewise.
* doc/features.tex: Likewise.
* doc/Makefile: Likewise.
* doc/dev.tex: Likewise.

SVN Revision: 618
2006-09-22 09:16:31 +00:00
Mickaël Rémond b7fc7fdada * doc/guide.tex: Large improvements for ejabberd 1.1.2 (thanks to Sander
Devrieze)
* doc/version.tex: Likewise.
* doc/features.tex: Likewise.
* doc/Makefile: Likewise.
* doc/dev.tex: Likewise.

SVN Revision: 617
2006-09-22 09:09:28 +00:00
Mickaël Rémond 8583e3f9be Bugfixes coming from trunk (SVN #607):
* doc/guide.tex: Minor fix on index generation.

SVN Revision: 616
2006-09-22 08:42:41 +00:00
Mickaël Rémond 23b78b0f0c Improvement coming from trunk (SVN #606):
* doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to
Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise
* src/eldap/eldap_filter.erl: Likewise

SVN Revision: 615
2006-09-22 08:36:09 +00:00
Mickaël Rémond 677a6004cf Improvement coming from trunk (SVN #605):
* src/odbc/mssql.sql: Removed unused fields.

SVN Revision: 614
2006-09-22 08:08:37 +00:00
Mickaël Rémond ec668cd85a Update coming from trunk (SVN #604):
* src/ejd2odbc.erl: Updated

SVN Revision: 613
2006-09-22 08:05:50 +00:00
Mickaël Rémond 0b660a42f4 Bugfixes coming from trunk (SVN #603):
* src/mod_muc/mod_muc_log.erl: bugfix.

SVN Revision: 612
2006-09-22 07:58:58 +00:00
Mickaël Rémond 8c8c7b1e3f Improvement coming from trunk (SVN #602):
* src/mod_muc/mod_muc_log.erl: Fixed html special characters
escaping, added new image buttons, chatroom titles now point to
xmpp: URIs (thanks to Badlop)

SVN Revision: 611
2006-09-22 07:35:55 +00:00
Mickaël Rémond 4211540272 Improvement coming from trunk (SVN #601):
* src/mod_muc/mod_muc.erl: It is now possible to configure the
MUC room history feature. A new option has been added in ejabberd muc
module configuration (history_size) to define the size of the history.
0 is used to disable the feature.
* src/mod_muc/mod_muc_room.erl: Likewise.
* doc/guide.tex: Likewise.

SVN Revision: 610
2006-09-22 07:25:18 +00:00
Mickaël Rémond 6bdabb0c3b Bugfixes coming from trunk (SVN #599):
* src/mod_shared_roster.erl: Bugfix
* src/mod_roster_odbc.erl: Bugfix

SVN Revision: 609
2006-09-22 07:13:42 +00:00
Mickaël Rémond 647acc7768 Created ejabberd-1.1.2 branch to prepare release.
SVN Revision: 608
2006-09-22 07:05:56 +00:00
Mickaël Rémond da53c02892 * doc/guide.tex: Minor fix on index generation.
SVN Revision: 607
2006-09-14 16:08:58 +00:00
Alexey Shchepin 7d2a1af9d9 * doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to
Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise
* src/eldap/eldap_filter.erl: Likewise

SVN Revision: 606
2006-09-14 02:54:21 +00:00
Mickaël Rémond 6acc96c171 * src/odbc/mssql.sql: Removed unused fields.
SVN Revision: 605
2006-09-11 15:38:48 +00:00
Alexey Shchepin a1d696c36b * src/ejd2odbc.erl: Updated
SVN Revision: 604
2006-09-10 19:25:13 +00:00
Alexey Shchepin 3bb802a650 * src/mod_muc/mod_muc_log.erl: Fixed html special characters
escaping, added new image buttons, chatroom titles now point to
xmpp: URIs (thanks to Badlop)

* src/ejabberd_listener.erl: Bugfix

SVN Revision: 603
2006-09-09 23:34:51 +00:00
Alexey Shchepin a5e4d5d550 * src/mod_muc/mod_muc_log.erl: Fixed html special characters
escaping, added new image buttons, chatroom titles now point to
xmpp: URIs (thanks to Badlop)

* src/ejabberd_listener.erl: Bugfix

SVN Revision: 602
2006-09-09 23:33:00 +00:00
Mickaël Rémond d97eaa26d5 * src/mod_muc/mod_muc.erl: It is now possible to configure the
MUC room history feature. A new option has been added in ejabberd muc
module configuration (history_size) to define the size of the history.
0 is used to disable the feature.
* src/mod_muc/mod_muc_room.erl: Likewise.
* doc/guide.tex: Likewise.

SVN Revision: 601
2006-09-05 15:53:54 +00:00
Alexey Shchepin 0fe9caa304 * src/ejabberd_socket.erl: All XML socket operations moved here
* src/ejabberd_s2s_in.erl: Likewise

SVN Revision: 600
2006-09-05 04:36:01 +00:00
Alexey Shchepin 4d7cc9301a * src/ejabberd_socket.erl: All XML socket operations moved here
* src/ejabberd_listener.erl: Updated
* src/ejabberd_receiver.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_s2s_out.erl: Likewise
* src/ejabberd_service.erl: Likewise

* src/mod_shared_roster.erl: Bugfix

* src/mod_roster_odbc.erl: Bugfix

SVN Revision: 599
2006-09-05 04:26:28 +00:00
Mickaël Rémond d0e2ac9ed2 * src/odbc/odbc_queries.erl: Support for Microsoft SQL Server as
a database backend (via ODBC).
* src/odbc/ejabberd_odbc.erl: Likewise.
* src/odbc/mssql.sql: Likewise.
* src/odbc/Makefile.in: Likewise.
* src/ejabberd_auth_odbc.erl: Likewise.
* src/mod_offline_odbc.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise.
* src/mod_last_odbc.erl: Likewise.
* src/configure.ac: Likewise
* doc/guide.tex: Likewise.
* doc/introduction.tex: Likewise.
* src/odbc/pg.sql: Minor fix.

SVN Revision: 598
2006-09-03 15:15:46 +00:00
Mickaël Rémond 40c8c8b559 * src/ejabberd.app: Updated to the current version.
SVN Revision: 597
2006-08-28 17:31:24 +00:00
Mickaël Rémond a7f22bfded * Makefile.in: Fix for MacOSX compilation (EJAB-124).
* ejabberd_zlib/Makefile.in: Likewise.
* mod_irc/Makefile.in: Likewise.
* stringprep/Makefile.in: Likewise.
* tls/Makefile.in: Likewise.

SVN Revision: 596
2006-08-28 14:56:13 +00:00
Alexey Shchepin d3b1bbda47 * src/mod_muc/mod_muc_room.erl: Fixed logging configuring
permission check

SVN Revision: 595
2006-08-27 17:46:27 +00:00
Alexey Shchepin 66561c3602 * src/ejabberd_router.erl: Now filter_packet hook works in
'global' context

SVN Revision: 594
2006-08-14 19:46:14 +00:00
Alexey Shchepin cc4d404364 * src/tls/tls_drv.c: Report OpenSSL error messages (thanks to
Magnus Henoch)

SVN Revision: 593
2006-08-04 02:25:10 +00:00
Alexey Shchepin 81f9ffcd45 * src/mod_muc/mod_muc_room.erl: Use standardized MUC room
configuration fields (thanks to Magnus Henoch and Andy Turner)

SVN Revision: 592
2006-08-04 01:57:51 +00:00
Alexey Shchepin ab2e19a399 * src/mod_roster_odbc.erl: Bugfix
SVN Revision: 591
2006-08-01 23:32:56 +00:00
Mickaël Rémond 9b8dcac5a7 * src/ejabberd_c2s.erl: Bugfix: added a missing catch and fixed
the error code used on unknown namespace.
* src/ejabberd_s2s_out.erl: Improved s2s connection negociation
(dialback namespace usage) when using tls.
* src/ejabberd_sm.erl: Fixed the error code used on unknown
namespace.
* src/mod_register.erl: ejabberd now sends iq result and stream
end on user remove.

SVN Revision: 590
2006-07-28 16:18:50 +00:00
Mickaël Rémond e965aae60a * src/acl.erl: The server does not crash anymore on wrong acl rule. Add
rule error message in log file (EJAB-118).

SVN Revision: 589
2006-07-18 12:29:17 +00:00
Mickaël Rémond 6290bac932 * src/web/ejabberd_web_admin.erl: User creation form now creates
the user for the current virual host only and does not require to type
the hostname (EJAB-116).
* src/jlib.erl: String to JID conversion now returns an error if
the JID string contains two arobases.

SVN Revision: 588
2006-07-07 08:06:12 +00:00
Mickaël Rémond 6225c951fd * src/mod_muc/mod_muc_room.erl: Fixed the order in which room presence
packets are sent (First existing presence to new occupant, then new
occupant presence to existing users (EJAB-115).

SVN Revision: 587
2006-07-06 16:06:44 +00:00
Mickaël Rémond a166203194 * src/mod_shared_roster.erl: Shared roster entries can now be
moved or rename from a client without breaking current presence status
(EJAB-70).
* src/mod_roster.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise.

SVN Revision: 586
2006-07-06 14:08:09 +00:00
Mickaël Rémond 6bf03a683d * src/ejabberd_auth.erl: Better handling of errors when the server for
authentication is unknown.

SVN Revision: 585
2006-07-05 14:36:21 +00:00
Mickaël Rémond 9fdef5c15f * src/mod_shared_roster.erl: The logged user (self) is now removed from
the shared roster list (EJAB-110).

SVN Revision: 584
2006-07-05 09:26:52 +00:00
Mickaël Rémond 110c5290b2 * src/mod_shared_roster.erl: Delete the shared roster group now
correctly delete its user entries (EJAB-112).

SVN Revision: 583
2006-07-05 08:52:21 +00:00
Mickaël Rémond 230d66b168 * src/web/ejabberd_web_admin.erl: More flexible parsing the shared
roster members list from the configuration form.

SVN Revision: 582
2006-07-05 08:09:35 +00:00
Alexey Shchepin 2b46e3eaa7 * src/ejabberd_s2s_in.erl: Removed needless check for xmlns:db
when starttls is enabled

SVN Revision: 581
2006-06-19 02:32:57 +00:00
Mickaël Rémond 7d0f9ddce2 * src/web/ejabberd_http_poll.erl: Bugfix: using tuple instead of a Pid
to send Erlang messages in a rarely used case.

SVN Revision: 580
2006-06-15 13:32:48 +00:00
Mickaël Rémond bd276f0b07 * src/ejabberd_auth.erl: Fixed broken multiple connection with
anonymous login problem (EJAB-107).
* src/ejabberd_auth_anonymous.erl: Likewise.

SVN Revision: 579
2006-06-13 16:52:38 +00:00
Mickaël Rémond b80002a8bb * src/mod_pubsub/mod_pubsub.erl: Discovery query on an item does
no more return an "item not found error". Discovery stop
correctly at the item level (Thanks to Christophe Romain) (EJAB-105).

SVN Revision: 578
2006-06-07 14:07:18 +00:00
Mickaël Rémond 9dcd3f4080 * src/mod_pubsub/mod_pubsub.erl: Support for pubsub node
creation ACL.  It is now possible to limit the node creation rights
using an ACL from ejabberd config file (Thanks to Christophe Romain)
(EJAB-104).
* doc/guide.tex: Likewise.
* src/ejabberd.cfg.example.

SVN Revision: 577
2006-06-07 08:38:37 +00:00
Mickaël Rémond 05c50cc5ca * src/web/ejabberd_http.erl: The web module now accepts HTTP
absolute URL (used behind a proxy). This apply to HTTP polling and to
the web interface (Thanks to Jean-Sebastien Pedron).

SVN Revision: 576
2006-06-02 15:02:39 +00:00
Mickaël Rémond ae537d2bfb * src/web/ejabberd_http_poll.erl: Messages polled between the
the last client request and the polling timeout were lost. Those
messages are now resent using ejabberd routing mechanisms (EJAB-87).

SVN Revision: 575
2006-06-02 13:16:21 +00:00
Mickaël Rémond ce5d10eb59 * src/mod_roster.erl: According to RFC3921 section 9.2, outbound
subscribe presence packets must be sent, even if the user has already
asked for subcription previously (subscription: none and pending: out).
The mod_roster now conforms to this behaviour (EJAB-102).
* src/mod_roster_odbc.erl: Likewise.

SVN Revision: 574
2006-05-29 16:48:20 +00:00
Mickaël Rémond 7ec4c9c119 * src/configure: removed debug printout.
SVN Revision: 573
2006-05-27 09:07:32 +00:00
Mickaël Rémond 02dd41c178 * src/configure.ac: Added an optional check for krb5.h in
configure. ejabberd can now be build directly on RedHat and OpenBSD
(EJAB-100).

SVN Revision: 572
2006-05-27 08:46:27 +00:00
Alexey Shchepin c4e98624c3 * src/msgs/sk.msg: Slovak translation (thanks to Juraj Michalek
and SkLUG)

SVN Revision: 571
2006-05-26 22:06:19 +00:00
Alexey Shchepin c737ce1b8c * src/odbc/pg.sql: Updated
* src/mod_shared_roster.erl: Updated

* src/ejabberd_sm.erl: Bugfix

* src/mod_roster.erl: Bugfix
* src/mod_roster_odbc.erl: Likewise

SVN Revision: 570
2006-05-26 00:00:32 +00:00
Mickaël Rémond 42cdf80cd4 * src/mod_roster.erl: The subscribe request are now resend at
login as long as they have not been answered. mod_roster do no more
depends on mod_offline.
* src/ejabberd_sm.erl: Likewise.
* src/ejabberd_c2s.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise (The ODBC/relational support
has not yet been tested).
* src/mod_roster.hrl: Likewise.
* src/mod_offline.erl: Likewise.
* src/mod_offline_odbc.erl: Likewise.
* odbc/pg.sql: Likewise.
* odbc/mysql.sql: Likewise.

SVN Revision: 569
2006-05-23 20:19:37 +00:00
Mickaël Rémond 753e768090 * src/ejabberd_sm.erl: The max_user_sessions has been moved to
host configuration.
* src/ejabberd.cfg.example: Likewise.
* doc/guide.tex: Likewise.

SVN Revision: 568
2006-05-22 10:17:22 +00:00
Mickaël Rémond 043c2ccf51 * src/ejabberd_sm.erl: An option to limit the number of opened sessions
for a given user have been added. As a default, a given user can only
log in 10 times with different resources. After that, new connections
replace the older ones.
* src/ejabberd.cfg.example: Likewise.
* doc/guide.tex: Likewise.

SVN Revision: 567
2006-05-21 16:34:32 +00:00
Mickaël Rémond 21b912a375 * src/web/ejabberd_http_poll.erl: Timeout disconnection were not
properly handled after "active once" migration. This is now fixed.

SVN Revision: 566
2006-05-15 15:45:52 +00:00
Mickaël Rémond 177b367cbe * src/ejabberd_ctl.erl: Added a way to delete the older message
from the offline message table. This function is only available if
offline message queue is stored in Mnesia internal database. The
function delete_old_messages is not available in mod_offline_odbc.

SVN Revision: 565
2006-05-07 21:26:06 +00:00
Mickaël Rémond 640e1579f5 * src/ejabberd_ctl.erl: Now prints a user-friendly when trying
to restore a backup from a non-existent file.

SVN Revision: 564
2006-05-07 17:31:12 +00:00
Mickaël Rémond 205c565e6f * src/ejabberd_admin.erl: Code refactoring. Common Mnesia database
restore function.

SVN Revision: 563
2006-05-07 16:57:11 +00:00
Mickaël Rémond ba55665381 * src/mod_configure.erl: Unknown tables or tables from now
unused modules are ignored during restore. The restore can now be
performed in such case (EJAB-80).
* src/web/ejabberd_web_admin.er: Likewise.
* src/ejabberd_admin.erl: Likewise. Code refactoring. Common Mnesia
database restore function.

SVN Revision: 562
2006-05-07 16:52:21 +00:00
Mickaël Rémond c57ca8a483 * src/ejabberd_ctl.erl: Unknown tables or tables from now unused
modules are ignored during restore. The restore can now be performed in
such case.

SVN Revision: 561
2006-05-01 09:55:03 +00:00
Mickaël Rémond 656c1c1570 * doc/release_note_1.1.1.txt: Fixed typo.
SVN Revision: 560
2006-04-28 11:16:14 +00:00
Mickaël Rémond 719306c999 * doc/guide.html: Updated generated HTML version of the guide.
SVN Revision: 558
2006-04-28 09:38:44 +00:00
Mickaël Rémond b86e1a9e5b * src/ejabberd.hrl: release 1.1.1
* doc/version.tex: Likewise
* doc/guide.tex: Likewise

SVN Revision: 557
2006-04-28 09:35:26 +00:00
Mickaël Rémond 960e66423d * Release not for security fix release.
SVN Revision: 556
2006-04-28 08:43:15 +00:00
Alexey Shchepin 66149f862f * src/cyrsasl.erl: Bugfix: anonymous authentication was always
enabled

SVN Revision: 555
2006-04-27 21:24:30 +00:00
Mickaël Rémond bd4c18516c * src/ejabberd_ctl.erl: The status now only returns "started" if
ejabberd is started and ready to accept requests.

SVN Revision: 554
2006-04-26 22:28:05 +00:00
Mickaël Rémond d3777ba590 * Fixed notes for version 1.1.0 release.
SVN Revision: 551
2006-04-24 21:32:31 +00:00
Alexey Shchepin 3e3d78effc * doc/guide.tex: Added a documentation for max_stanza_size option
SVN Revision: 550
2006-04-24 19:25:09 +00:00
Mickaël Rémond 7483d337b4 * src/ejabberd.hrl: Updated for version 1.1.0.
SVN Revision: 549
2006-04-24 13:04:21 +00:00
Mickaël Rémond ccd28e0e5b * Updated documentation before 1.1.0 release
SVN Revision: 548
2006-04-23 22:00:06 +00:00
Mickaël Rémond f01dea67a0 * Updated documentation before 1.1.0 release
SVN Revision: 547
2006-04-23 21:47:18 +00:00
Mickaël Rémond 35c42e2592 doc/release_notes_1.1.0.txt: Preparing release 1.1.0
SVN Revision: 546
2006-04-23 20:43:40 +00:00
Mickaël Rémond 29cec06fea * src/guide.tex: Added documentation for ODBC and native database
configuration (PostgreSQL and MySQL).

SVN Revision: 545
2006-04-23 20:04:40 +00:00
Alexey Shchepin 957ddb325e * doc/guide.tex: Updated (thanks to Sander Devrieze)
* doc/introduction.tex: Likewise

SVN Revision: 544
2006-04-23 16:55:25 +00:00
Alexey Shchepin 1694a6a756 * src/configure: Removed '==' bashism (thanks to Magnus Henoch)
* src/aclocal.m4: Likewise

SVN Revision: 543
2006-04-23 15:35:14 +00:00
Alexey Shchepin 25ca5013bb * src/ejabberd_c2s.erl: Resend messages when leaving negative
presence state (thanks to Magnus Henoch)

SVN Revision: 542
2006-04-23 14:57:37 +00:00
Mickaël Rémond c86275ce9d * src/ejabberd_sm.erl: Now delivering messages to all resources with
max equal priority when this priority is non-negative.

SVN Revision: 541
2006-04-23 09:31:54 +00:00
Mickaël Rémond dc198cf08e * src/msgs/de.msg: Updated (thanks to Nikolaus Polak)
SVN Revision: 540
2006-04-23 09:26:41 +00:00
Mickaël Rémond d5309f0af4 * src/msgs/fr.msg: updated
SVN Revision: 539
2006-04-23 08:56:46 +00:00
Alexey Shchepin 8eb823abbb * src/msgs/pt-br.msg: Updated (thanks to Lucius Curado)
* src/xml_stream.erl: Support for stanza size limit (thanks to
Igor Goryachev)
* src/ejabberd_receiver.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd.cfg.example: Updated

* src/ejabberd_auth.erl: Fixed try_register/3 behaviour

SVN Revision: 538
2006-04-22 04:02:42 +00:00
Alexey Shchepin c4b976881e * src/msgs/pt-br.msg: Updated (thanks to Lucius Curado)
* src/xml_stream.erl: Support for stanza size limit (thanks to
Igor Goryachev)
* src/ejabberd_receiver.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd.cfg.example: Updated

* src/ejabberd_auth.erl: Fixed try_register/3 behaviour

SVN Revision: 537
2006-04-22 03:35:13 +00:00
Mickaël Rémond e0c700e725 * doc/guide.tex: Updated (SASL anonymous and anonymous login).
SVN Revision: 536
2006-04-20 22:12:09 +00:00
Mickaël Rémond 8688bc508f * src/ejabberd_auth.erl: Bugfix: We now are forced to check is an
anonymous user is log under a given user name before trying to register
it.

SVN Revision: 535
2006-04-20 17:51:29 +00:00
Mickaël Rémond 3cce56b9f8 * src/ejabberd.cfg.example: Update of the example for anonymous.
Removed unnecessary parameter.
* src/ejabberd_auth_anonymous.erl: Removed unnecessary parameter
and bugfix.

SVN Revision: 534
2006-04-20 15:42:51 +00:00
Alexey Shchepin 4797060eb9 * src/msgs/pl.msg: Updated (thanks to Andrzej Smyk)
SVN Revision: 533
2006-04-18 22:28:30 +00:00
Alexey Shchepin 15a714ae68 * src/xml_stream.erl: Cleanup
* src/ejabberd_service.erl: Updated to use ejabberd_receiver

SVN Revision: 532
2006-04-13 02:08:24 +00:00
Alexey Shchepin aa8e532cbd * src/ejabberd_c2s.erl: Minor fix
* src/ejabberd_c2s.erl: Bugfix (thanks to Sergei Golovan)

* doc/guide.tex: Updated (thanks to Sergei Golovan)

* src/msgs/nl.msg: Updated (thanks to Sander Devrieze)

SVN Revision: 531
2006-04-12 02:54:27 +00:00
Alexey Shchepin 681f0b9567 * src/msgs/es.msg: Updated (thanks to Badlop)
* src/ejabberd.cfg.example: Updated (thanks to Badlop)

* doc/guide.tex: Updated (thanks to Badlop)

SVN Revision: 530
2006-04-11 03:16:34 +00:00
Mickaël Rémond 49917e284e * src/mod_pubsub/mod_pubsub.erl: Fixed pubsub root node creation
(conflict error was send on node creation)

SVN Revision: 529
2006-04-07 13:39:48 +00:00
Alexey Shchepin 0e1f86e1f5 * src/ejabberd_sm.erl: SASL Anonymous + Anonymous login support
(thanks to Mickael Remond and Magnus Henoch)
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_auth.erl: Likewise
* src/ejabberd_auth_anonymous.erl: Likewise
* src/cyrsasl.erl: Likewise
* src/cyrsasl_anonymous.erl: Likewise
* src/ejabberd.cfg.example: Likewise

SVN Revision: 528
2006-04-07 00:51:53 +00:00
Alexey Shchepin 9a44fdffab * src/ejabberd_sm.erl: SASL Anonymous + Anonymous login support
(thanks to Mickael Remond and Magnus Henoch)
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_auth.erl: Likewise
* src/ejabberd_auth_anonymous.erl: Likewise
* src/cyrsasl.erl: Likewise
* src/cyrsasl_anonymous.erl: Likewise
* src/ejabberd.cfg.example: Likewise

SVN Revision: 527
2006-04-07 00:39:24 +00:00
Alexey Shchepin a5a978946d * src/expat_erl.c: Use binaries for CDATA
* src/xml.erl: Likewise

SVN Revision: 526
2006-04-05 23:56:16 +00:00
Alexey Shchepin ed43f8024c * src/msgs/ru.msg: Updated (thanks to Sergei Golovan)
* src/msgs/uk.msg: Likewise

* src/mod_configure.erl: Updated translation strings (thanks to
Sergei Golovan)
* src/web/ejabberd_web_admin.erl: Likewise

* src/ejabberd_s2s_in.erl: Changed a path to PKIX includes (thanks
to Sergei Golovan)

SVN Revision: 525
2006-04-01 21:43:52 +00:00
Mickaël Rémond 3e9e51ee30 * src/Makefile.in: Added an option to compile ejabberd with debug
print-out (make ejabberd_debug=true).
* src/ejabberd_c2s.erl: Added a new debug printout: We can now
print the XML packets send by the server.

SVN Revision: 524
2006-04-01 16:05:20 +00:00
Alexey Shchepin f3085d09e4 * src/ejabberd_c2s.erl: Bugfix
SVN Revision: 523
2006-03-30 23:30:05 +00:00
Alexey Shchepin 832279f1f2 * src/ejabberd_c2s.erl: Enable zlib only over TCP
SVN Revision: 522
2006-03-25 21:16:49 +00:00
Alexey Shchepin 89e57d421e * src/ejabberd_c2s.erl: Bugfix (thanks to Sergei Golovan)
SVN Revision: 521
2006-03-18 20:10:56 +00:00
Alexey Shchepin a6b3e6c904 * src/mod_privacy.erl: Fixed privacy list pushing (thanks to
Sergei Golovan)
* src/ejabberd_c2s.erl: Likewise

SVN Revision: 520
2006-03-17 04:35:03 +00:00
Mickaël Rémond eabf8d1c6c * src/ejabberd_sm.erl: Remove unnecessary use delete_object,
which can, in some context break indexes, when used on non bag table.

SVN Revision: 519
2006-03-16 09:59:45 +00:00
Alexey Shchepin 7278a606a8 * src/ejabberd_c2s.erl: Bugfix
SVN Revision: 518
2006-03-16 01:02:44 +00:00
Alexey Shchepin d5137b0563 * src/xml_stream.erl: Added catching of gen_fsm:send_event errors
* src/ejabberd_s2s_out.erl: Better support for multiple SRV
records (thanks to Sergei Golovan)

* src/mod_muc/mod_muc_log.erl: Support for chatroom logging
(thanks to Badlop)
* src/mod_muc/mod_muc_room.erl: Likewise
* src/mod_muc/Makefile.in: Likewise
* src/mod_muc/Makefile.win32: Likewise

* src/gen_iq_handler.erl: Added support for {queues, N} IQ handler
type

SVN Revision: 517
2006-03-14 04:26:15 +00:00
Alexey Shchepin 4f2a355408 * src/mod_muc/mod_muc_room.erl: Bugfix
SVN Revision: 516
2006-03-06 02:30:15 +00:00
Alexey Shchepin d837cb9e8d * src/ejabberd_c2s.erl: Bugfix
* src/ejabberd_auth_odbc.erl: Bugfix

SVN Revision: 515
2006-03-05 04:55:43 +00:00
Mickaël Rémond 804c32c100 * src/ejabberd_logger_h.erl: reopen-log function now rename the log
file if it has not been already renamed by a logrotate process.  This
change allow ejabberd administrators to rotate log files on Windows
(EJAB-52).

SVN Revision: 514
2006-03-04 17:33:23 +00:00
Alexey Shchepin 17420e058b * src/web/ejabberd_web_admin.erl: Added a interface for node
updating
* src/ejabberd_update.erl: Added function update_info/0

SVN Revision: 513
2006-02-27 04:47:30 +00:00
Alexey Shchepin 20d049fe28 * src/web/ejabberd_web_admin.erl: Added a interface for node
updating
* src/ejabberd_update.erl: Added function update_info/0

SVN Revision: 512
2006-02-27 04:43:16 +00:00
Alexey Shchepin 51c4ab8351 * src/msgs/ru.msg: Updated (thanks to Sergei Golovan)
* src/msgs/uk.msg: Likewise

* contrib/extract_translations/extract_translations.erl: Minor
update (thanks to Sergei Golovan)

* src/ejabberd_zlib/Makefile.win32: Zlib support for Windows build
(thanks to Sergei Golovan)
* src/Makefile.win32: Likewise
* src/configure.erl: Likewise

SVN Revision: 511
2006-02-25 19:48:17 +00:00
Alexey Shchepin 80181dc618 * src/ejabberd_ctl.erl: Added API for virtual host specific
commands, removed registered-users command
* src/ejabberd_auth.erl: Added processing of registered-users
command
* src/ejabberd_auth_internal.erl: Likewise
* src/ejabberd_auth_ldap.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberd_app.erl: Added inititalization of ejabberd_ctl
* src/ejabberd_sm.erl: Minor update

SVN Revision: 510
2006-02-20 04:07:42 +00:00
Alexey Shchepin 0fd01cfff8 * src/mod_irc/mod_irc_connection.erl: Added handling for "%", "&"
and "~" prefixes in IRC nicknames (thanks to Vladimir Kulev)

SVN Revision: 509
2006-02-18 20:05:05 +00:00
Alexey Shchepin 44fdd72041 * src/mod_irc/mod_irc_connection.erl: Added /msg and /ctcp
commands, improved handling of channel topic and kick, mirc colors
now filtered, other minor improvements (thanks to Oleg
V. Motienko)

* src/mod_configure.erl: Improved strings (thanks to Sander
Devrieze)
* src/mod_vcard.erl: Likewise
* src/mod_vcard_ldap.erl: Likewise
* src/mod_vcard_odbc.erl: Likewise
* src/web/ejabberd_web_admin.erl: Likewise

SVN Revision: 508
2006-02-18 19:56:16 +00:00
Alexey Shchepin 0234b83be4 * src/mod_service_log.erl: Bugfix (thanks to Badlop)
* src/jd2ejd.erl: Use mod_vcard_odbc if it is loaded (thanks to
Tomasz Sterna)

* src/ejabberd_sm.erl: Added a few ejabberdctl commands

* src/ejabberd_ctl.erl: New interface for command registration

SVN Revision: 507
2006-02-15 04:15:54 +00:00
Alexey Shchepin 904300ce04 * src/ejabberd_ctl.erl: Partially rewritten
* src/ejabberd_ctl.hrl: Definitions of exit status codes moved
here

SVN Revision: 506
2006-02-13 04:05:13 +00:00
Alexey Shchepin 3eb1b59e64 * src/ejabberd_ctl.erl: Partially rewritten
SVN Revision: 505
2006-02-13 04:02:59 +00:00
Alexey Shchepin 73073d4af5 * src/mod_roster_odbc.erl: Bugfix
SVN Revision: 504
2006-02-12 18:40:03 +00:00
Alexey Shchepin 6eac737807 * src/mod_roster_odbc.erl: Bugfix
SVN Revision: 503
2006-02-11 23:55:21 +00:00
Alexey Shchepin b8e99a3c05 * src/odbc/ejabberd_odbc.erl: Reconnect on ODBC connection closing
SVN Revision: 502
2006-02-08 03:55:30 +00:00
Mickaël Rémond 2f0cf8bce8 * src/ejabberd_auth.erl: plugin authentication modules can now
be used in the configuration file: {auth_method, module} will
now use the module ejabberd_auth_module.erl for authentication.
(EJAB-45)

SVN Revision: 501
2006-02-07 17:35:32 +00:00
Alexey Shchepin 0cc168a9aa * src/mod_muc/mod_muc_room.erl: Kick non-members when room
reconfigured to be member-only or when membership is revoked in
member-only room, allowed "true" and "false" in x:data forms
(thanks to Sergei Golovan)

* src/mod_configure.erl: Added "Host" parameter to get_form and
set_form functions (thanks to Sergei Golovan)

* src/ejabberd_s2s_out.erl: Bugfix

* src/msgs/es.msg: Updated (thanks to Badlop)

* src/web/ejabberd_web.erl: Bugfix (thanks to Badlop)

* src/mod_irc/mod_irc.erl: Updated copyright dates
* src/mod_muc/mod_muc.erl: Likewise
* src/mod_pubsub/mod_pubsub.erl: Likewise
* src/mod_vcard.erl: Likewise
* src/mod_vcard_ldap.erl: Likewise
* src/mod_vcard_odbc.erl: Likewise
* src/web/ejabberd_web_admin.erl: Likewise

SVN Revision: 500
2006-02-06 05:12:54 +00:00
Alexey Shchepin cf10843071 * src/ejabberd_auth_odbc.erl: Added ejabberd_odbc_sup to ejabberd
supervision tree

SVN Revision: 499
2006-02-03 04:41:03 +00:00
Alexey Shchepin dc57e75e8f * src/web/ejabberd_http.erl: Authentication check moved to
ejabberd_web.erl
* src/web/ejabberd_web.erl: Likewise

* src/web/Makefile.in: Added ejabberd_http.hrl dependency

* src/web/ejabberd_http_poll.erl: Updated to use {active, once}
socket mode

* src/mod_irc/mod_irc.erl: Updated to use gen_server behaviour and
ejabberd supervision tree
* src/mod_irc/mod_irc_connection.erl: Likewise

SVN Revision: 498
2006-02-03 03:28:15 +00:00
Mickaël Rémond facefdb6e4 --prefix option can now override the default install dir (EJAB-43)
SVN Revision: 497
2006-02-02 16:59:38 +00:00
Alexey Shchepin 971001e7aa * src/mod_pubsub/mod_pubsub.erl: Updated to use gen_server
behaviour and ejabberd supervision tree

* src/mod_muc/mod_muc.erl: Added a supervisor for conference room
processes
* src/mod_muc/mod_muc_room.erl: Likewise

SVN Revision: 496
2006-02-02 05:00:27 +00:00
Alexey Shchepin 50f44530d2 * src/odbc/pg.sql: Fixed syntax error
* src/ejabberd_router.erl: Updated to use gen_server behaviour
* src/ejabberd_sm.erl: Likewise
* src/ejabberd_s2s.erl: Likewise
* src/gen_iq_handler.erl: Likewise

* src/ejabberd_sup.erl: Added supervisor for ejabberd_receiver
* src/ejabberd_receiver.erl: Updated

SVN Revision: 495
2006-01-29 04:38:31 +00:00
Alexey Shchepin ef456ab645 * src/ejabberd_update.erl: Support for run-time ejabberd updating
(not completed)
* src/ejabberd_c2s.erl: Added 'update_info' module attribute for
testing ejabberd_update

SVN Revision: 494
2006-01-27 05:02:33 +00:00
Alexey Shchepin 90618de7be * src/ejabberd_update.erl: Support for run-time ejabberd updating
(not completed)
* src/ejabberd_c2s.erl: Added 'update_info' module attribute for
testing ejabberd_update

SVN Revision: 493
2006-01-27 05:00:27 +00:00
Alexey Shchepin efacb0ad66 * src/ejabberd_c2s.erl: Remove top-level xmlns from incoming
stanzas
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_service.erl: Likewise

SVN Revision: 492
2006-01-25 00:35:12 +00:00
Alexey Shchepin 976a616662 * src/ejabberd_c2s.erl: Better error handling for stream
compression support (thanks to Sergei Golovan)

SVN Revision: 491
2006-01-24 22:52:23 +00:00
Alexey Shchepin ef0b3ab24c * src/mod_roster_odbc.erl: Bugfix
SVN Revision: 490
2006-01-24 21:47:53 +00:00
Alexey Shchepin 2d7fbf3964 Fixed ChangeLog
SVN Revision: 489
2006-01-24 02:55:44 +00:00
Alexey Shchepin a4caafeb72 * src/ejabberd_sm.erl: Partially rewritten to work more
efficiently and avoid race conditions
* src/ejabberd_c2s.erl: Likewise

* src/mod_irc/mod_irc_connection.erl: Cleanup

SVN Revision: 488
2006-01-23 23:13:06 +00:00
Mickaël Rémond 1295893864 * src/ejabberd_receiver.erl: Added new debugging trace: It is now
possible to dump the XML stream received from a client (usefull for
client debugging).

SVN Revision: 487
2006-01-20 16:51:58 +00:00
Alexey Shchepin 568909d5bb * src/aclocal.m4: Updated for zlib support
* src/configure.ac: Likewise

* src/mod_muc/mod_muc_room.erl: Weakened presence filtering, added
warning in non-anonymous rooms, room destroying updated to latest
JEP-0045, added a number of occupants and room name in room's
disco#info reply, miscellaneous internal changes (thanks to Sergei
Golovan)

* src/mod_muc/mod_muc.erl: Better support for nick unregistration
(thanks to Sergei Golovan)

* src/ejabberd_zlib/ejabberd_zlib.erl: Zlib support (thanks to
Sergei Golovan)
* src/ejabberd_zlib/ejabberd_zlib_drv.c: Likewise
* src/ejabberd_zlib/Makefile.in: Likewise
* src/ejabberd_c2s.erl: Stream compression support (JEP-0138)
* src/ejabberd_receiver.erl: Likewise

* src/mod_disco.erl: Don't split node name before calling hooks
(thanks to Sergei Golovan)

* src/mod_configure.erl: Support for configuration using ad-hoc
commands (thanks to Sergei Golovan)

* src/mod_announce.erl: Support for sending announce messages
using ad-hoc commands (thanks to Sergei Golovan)

* src/mod_adhoc.erl: Ad-hoc support (JEP-0050) (thanks to Magnus
Henoch)
* src/adhoc.erl: Likewise
* src/adhoc.hrl: Likewise

* src/jlib.hrl: Updated (thanks to Sergei Golovan)

* src/gen_mod.erl: Added function is_loaded/2 (thanks to Sergei
Golovan)

* src/ejabberd_service.erl: Changed error message on handshake
error (thanks to Sergei Golovan)

* src/ejabberd.cfg.example: Updated (thanks to Sergei Golovan)

SVN Revision: 486
2006-01-19 02:17:31 +00:00
Mickaël Rémond c7bafe0056 * src/odbc/ejabberd_odbc.erl: Added a way to retry database
connection connect for 5 minutes when the connection is lost. No further
connection is retry after 5 minutes (EJAB-22 & EJAB-25)
* src/odbc/ejabberd_odbc_sup.erl: likewise

SVN Revision: 485
2006-01-13 18:05:10 +00:00
Mickaël Rémond 54a1ced6f6 * src/odbc/ejabberd_odbc.erl: underscore and percent are now only
escaped in like queries. MySQL where not escaping those escaped
characters in other context (EJAB-24)
* src/mod_vcard_odbc.erl: likewise.
* src/odbc/mysql.sql: Fixed MySQL database creation script: Was
not properly working with all MySQL version.

SVN Revision: 484
2006-01-13 10:59:52 +00:00
Alexey Shchepin 6bb510d99e * src/ejabberd_service.erl: Bugfix
* src/ejabberd_receiver.erl: Rewritten to use {active, once} mode
for socket
* src/ejabberd_c2s.erl: Update
* src/ejabberd_listener.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_s2s_out.erl: Likewise
* src/ejabberd_service.erl: Likewise
* src/shaper.erl: Likewise
* src/tls/tls.erl: Likewise
* src/web/ejabberd_http.erl: Likewise

SVN Revision: 483
2006-01-13 01:55:20 +00:00
Mickaël Rémond 8401a5ac55 Native MySQL support. The Erlang MySQL module is needed:
http://support.process-one.net/doc/display/CONTRIBS/Yxa

SVN Revision: 482
2006-01-02 17:39:04 +00:00
Alexey Shchepin 7ee3e45df1 * src/ejabberd_logger_h.erl: Speed optimizations
SVN Revision: 481
2005-12-24 02:40:35 +00:00
Mickaël Rémond 784edef4b6 Added missing file.
SVN Revision: 480
2005-12-23 13:52:39 +00:00
Alexey Shchepin 254a8c1b60 * src/Makefile.in: Clean results of ASN.1 compiler (thanks to
Sergei Golovan)

SVN Revision: 479
2005-12-22 14:48:01 +00:00
Alexey Shchepin 3cbed307bc * src/win32/ejabberd.nsh: Removed (thanks to Sergei Golovan)
SVN Revision: 478
2005-12-22 13:44:58 +00:00
Alexey Shchepin 08af86cb11 * src/Makefile.win32: Updated (thanks to Sergei Golovan)
* src/configure.erl: Likewise
* src/win32/CheckReqs.ini: Likewise
* src/win32/CheckReqs1.ini: Likewise
* src/win32/CheckReqs1H.ini: Likewise
* src/win32/ejabberd.nsh: Likewise
* src/win32/ejabberd.nsi: Likewise

SVN Revision: 477
2005-12-22 13:35:54 +00:00
Mickaël Rémond 15e747782a * src/odbc/ejabberd_odbc.erl: Added error message on ODBC
connection to help ODBC configuration troubleshooting.

SVN Revision: 476
2005-12-22 09:40:21 +00:00
Alexey Shchepin 0035c63399 * src/odbc/ejabberd_odbc.erl: Bugfix
SVN Revision: 475
2005-12-22 05:38:04 +00:00
Mickaël Rémond 559b5cae14 * src/odbc/mysql.sql: Database description for MySQL Database
4.1 (Max version, with Innodb)
* src/ejabberd_auth_odbc.erl: MySQL ODBC support.
* src/mod_offline_odbc.erl: likewise
* src/mod_vcard_odbc.erl: likewsie
* src/mod_roster_odbc.erl: likewise
* src/odbc/ejabberd_odbc.erl: likewise
* src/mod_last_odbc.erl: likewise

SVN Revision: 474
2005-12-21 16:10:56 +00:00
Mickaël Rémond c275a24597 Documentation update, code clean-up and Makefile improvement.
SVN Revision: 473
2005-12-16 16:33:42 +00:00
Alexey Shchepin 52fcdc716d * doc/features.tex: Added feature sheet (Thanks to Sander
Devrieze)

SVN Revision: 472
2005-12-15 23:31:49 +00:00
Mickaël Rémond 9064851ab5 Documentation updates for actual ejabberd 1.0.0 release.
SVN Revision: 471
2005-12-15 08:57:33 +00:00
Alexey Shchepin 5f2de53f01 * src/mod_irc/iconv_erl.c: Bugfix (thanks to Tomas Janousek)
SVN Revision: 470
2005-12-14 01:32:48 +00:00
Alexey Shchepin 689842edf8 * src/win32/ejabberd.nsi: Updated (thanks to Maxim Ryazanov)
SVN Revision: 468
2005-12-13 20:31:22 +00:00
Alexey Shchepin aae0124f61 * src/win32/ejabberd.nsi: Updated (thanks to Sergei Golovan)
SVN Revision: 467
2005-12-13 20:29:00 +00:00
133 changed files with 21488 additions and 7723 deletions
+885 -1
View File
@@ -1,5 +1,889 @@
2006-09-27 Mickael Remond <mickael.remond@process-one.net>
* doc/release_notes_1.1.2.txt: Minor fixes.
* doc/guide.tex: Fixed Latexish typos.
2006-09-26 Mickael Remond <mickael.remond@process-one.net>
* doc/release_notes_1.1.2.txt: Draft release notes.
* src/msgs/pl.msg: Updated (thanks to Andrzej Smyk).
* src/ejabberd_s2s.erl: Added incoming-s2s-number and
outgoing-s2s-number ejabberdctl commands
* src/mod_muc/mod_muc_room.erl: Minor english update.
* src/msgs/pl.msg: Likewise.
* src/msgs/uk.msg: Likewise.
* src/msgs/pt-br.msg: Likewise.
* src/msgs/cs.msg: Likewise.
* src/msgs/ru.msg: Likewise.
* src/msgs/es.msg: Likewise.
* src/msgs/fr.msg: Likewise.
* src/msgs/de.msg: Likewise.
* src/msgs/nl.msg: Likewise.
* src/msgs/pt-br.msg: Updated Brazilian translation (thanks to
Renato Botelho) and clean-up.
2006-09-25 Mickael Remond <mickael.remond@process-one.net>
* src/msgs/de.msg: Updated German translation (thanks to Nikolaus
Polak).
* src/ejabberd.app: Version update for ejabberd 1.1.2.
* src/msgs/pl.msg: Updated Polish translation (thanks to Zbyszek
Zolkiewski).
* src/msgs/de.msg: Updated German translation (thanks to Marvin
Preuss).
* src/msgs/nl.msg: Updated Dutch translation (thanks to Sander
Devrieze).
* src/msgs/cs.msg: Updated Czech translation and removed unused
strings.
* doc/introduction.tex: Minor doc updates for release 1.1.2.
2006-09-24 Mickael Remond <mickael.remond@process-one.net>
* src/msgs/es.msg: Updated Spanish translation (thanks to Badlop).
* src/mod_muc/mod_muc_room.erl: Strings update (thanks to Serguei
Golovan).
* src/msgs/ru.msg: Updated Russian translation (thanks to Serguei
Golovan).
* src/msgs/uk.msg: Updated Ukrainian translation (thanks to Serguei
Golovan).
* src/msgs/fr.msg: Updated French translation.
* src/doc/guide.html: Minor W3C compliance fix in an Hevea
generated URL.
* src/doc/features.html: Added to be consistent (guide.html is in the
repository to make Latex optional, but still allow access to the doc).
2006-09-23 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd.hrl: Updated to version 1.1.2
2006-09-23 Alexey Shchepin <alexey@sevcom.net>
* src/eldap/eldap.erl: Enable the keepalive socket option (thanks
to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Now uses two LDAP connections
(thanks to Evgeniy Khramtsov)
* src/eldap/eldap_filter.erl: Bugfix (thanks to Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise
2006-09-22 Mickael Remond <mickael.remond@process-one.net>
* src/msgs/cs.msg: Added Czech translation (thanks to Milos Svasek).
* src/mod_muc/mod_muc.erl: Component name is now more user friendly
(thanks to Badlop).
* src/mod_irc/mod_irc.erl: Likewise.
* src/mod_pubsub/mod_pubsub.erl: Likewise.
* src/msgs/fr.msg: updated French translation.
* doc/guide.tex: Large improvements for ejabberd 1.1.2 (thanks to
Sander Devrieze)
* doc/version.tex: Likewise.
* doc/features.tex: Likewise.
* doc/Makefile: Likewise.
* doc/dev.tex: Likewise.
2006-09-14 Mickael Remond <mickael.remond@process-one.net>
* doc/guide.tex: Minor fix on index generation.
2006-09-14 Alexey Shchepin <alexey@sevcom.net>
* doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to
Evgeniy Khramtsov)
* src/mod_vcard_ldap.erl: Likewise
* src/eldap/eldap_filter.erl: Likewise
2006-09-11 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/mssql.sql: Removed unused fields.
2006-09-10 Alexey Shchepin <alexey@sevcom.net>
* src/ejd2odbc.erl: Updated
* src/mod_muc/mod_muc_log.erl: Fixed html special characters
escaping, added new image buttons, chatroom titles now point to
xmpp: URIs (thanks to Badlop)
2006-09-05 Mickael Remond <mickael.remond@process-one.net>
* src/mod_muc/mod_muc.erl: It is now possible to configure the MUC room
history feature. A new option has been added in ejabberd muc module
configuration (history_size) to define the size of the history. 0 is
used to disable the feature.
* src/mod_muc/mod_muc_room.erl: Likewise.
* doc/guide.tex: Likewise.
2006-09-05 Alexey Shchepin <alexey@sevcom.net>
* src/mod_shared_roster.erl: Bugfix
* src/mod_roster_odbc.erl: Bugfix
2006-09-03 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/odbc_queries.erl: Support for Microsoft SQL Server as a
database backend (via ODBC).
* src/odbc/ejabberd_odbc.erl: Likewise.
* src/odbc/mssql.sql: Likewise.
* src/odbc/Makefile.in: Likewise.
* src/ejabberd_auth_odbc.erl: Likewise.
* src/mod_offline_odbc.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise.
* src/mod_last_odbc.erl: Likewise.
* src/configure.ac: Likewise
* doc/guide.tex: Likewise.
* doc/introduction.tex: Likewise.
* src/odbc/pg.sql: Minor fix.
2006-08-28 Mickael Remond <mickael.remond@process-one.net>
* Makefile.in: Fix for MacOSX compilation.
* ejabberd_zlib/Makefile.in: Likewise.
* mod_irc/Makefile.in: Likewise.
* stringprep/Makefile.in: Likewise.
* tls/Makefile.in: Likewise.
* src/ejabberd.app: Updated to the current version.
2006-08-27 Alexey Shchepin <alexey@sevcom.net>
* src/mod_muc/mod_muc_room.erl: Fixed logging configuring
permission check
2006-08-14 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_router.erl: Now filter_packet hook works in
'global' context
2006-08-04 Alexey Shchepin <alexey@sevcom.net>
* src/tls/tls_drv.c: Report OpenSSL error messages (thanks to
Magnus Henoch)
* src/mod_muc/mod_muc_room.erl: Use standardized MUC room
configuration fields (thanks to Magnus Henoch and Andy Turner)
2006-08-02 Alexey Shchepin <alexey@sevcom.net>
* src/mod_roster_odbc.erl: Bugfix
2006-07-28 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_c2s.erl: Bugfix: added a missing catch and fixed the
error code used on unknown namespace.
* src/ejabberd_s2s_out.erl: Improved s2s connection negociation
(dialback namespace usage) when using tls.
* src/ejabberd_sm.erl: Fixed the error code used on unknown namespace.
* src/mod_register.erl: ejabberd now sends iq result and stream end on
user remove.
2006-07-16 Mickael Remond <mickael.remond@process-one.net>
* src/acl.erl: The server does not crash anymore on wrong acl rule. Add
rule error message in log file.
2006-07-07 Mickael Remond <mickael.remond@process-one.net>
* src/web/ejabberd_web_admin.erl: User creation form now creates the
user for the current virual host only and does not require to type
the hostname.
* src/jlib.erl: String to JID conversion now returns an error if the
JID string contains two arobases.
2006-07-06 Mickael Remond <mickael.remond@process-one.net>
* src/mod_shared_roster.erl: Shared roster entries can now be moved or
rename from a client without breaking current presence status.
* src/mod_roster.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise.
* src/mod_muc/mod_muc_room.erl: Fixed the order in which room presence
packets are sent (First existing presence to new occupant, then new
occupant presence to existing users.
2006-07-05 Mickael Remond <mickael.remond@process-one.net>
* src/web/ejabberd_web_admin.erl: More flexible parsing the shared
roster members list from the configuration form.
* src/mod_shared_roster.erl: Deletng a shared roster group now
correctly deletes its user entries.
* src/mod_shared_roster.erl: The logged user (self) is now removed from
the shared roster list.
* src/ejabberd_auth.erl: Better handling of errors when the server for
authentication is unknown.
2006-06-19 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_s2s_in.erl: Removed needless check for xmlns:db
when starttls is enabled
2006-06-15 Mickael Remond <mickael.remond@process-one.net>
* src/web/ejabberd_http_poll.erl: Bugfix: using tuple instead of a Pid
to send Erlang messages in a rarely used case.
2006-06-13 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_auth.erl: Fixed broken multiple connection with
anonymous login problem.
* src/ejabberd_auth_anonymous.erl: Likewise.
2006-06-07 Mickael Remond <mickael.remond@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: Support for pubsub node creation
ACL. It is now possible to limit the node creation rights using an
ACL from ejabberd config file (Thanks to Christophe Romain).
* doc/guide.tex: Likewise.
* src/ejabberd.cfg.example.
* src/mod_pubsub/mod_pubsub.erl: Discovery query on an item does
no more return an "item not found error". Discovery stop correctly
at the item level (Thanks to Christophe Romain).
2006-06-02 Mickael Remond <mickael.remond@process-one.net>
* src/web/ejabberd_http_poll.erl: Messages polled between the
the last client request and the polling timeout were lost. Those
messages are now resent using ejabberd routing mechanisms.
* src/web/ejabberd_http.erl: The web module now accepts HTTP
absolute URL (used behind a proxy). This apply to HTTP polling and
to the web interface (Thanks to Jean-Sebastien Pedron).
2006-05-29 Mickael Remond <mickael.remond@process-one.net>
* src/mod_roster.erl: According to RFC3921 section 9.2, outbound
subscribe presence packets must be sent, even if the user has already
asked for subcription previously (subscription: none and pending: out).
The mod_roster now conforms to this behaviour.
* src/mod_roster_odbc.erl: Likewise.
2006-05-27 Mickael Remond <mickael.remond@process-one.net>
* src/configure.ac: Added an optional check for krb5.h in
configure. ejabberd can now be build directly on RedHat and
OpenBSD.
2006-05-27 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/sk.msg: Slovak translation (thanks to Juraj Michalek
and SkLUG)
2006-05-26 Alexey Shchepin <alexey@sevcom.net>
* src/odbc/pg.sql: Updated
* src/mod_shared_roster.erl: Updated
* src/ejabberd_sm.erl: Bugfix
* src/mod_roster.erl: Bugfix
* src/mod_roster_odbc.erl: Likewise
2006-05-23 Mickael Remond <mickael.remond@process-one.net>
* src/mod_roster.erl: The subscribe request are now resend at login as
long as they have not been answered. mod_roster do no more depends on
mod_offline.
* src/ejabberd_sm.erl: Likewise.
* src/ejabberd_c2s.erl: Likewise.
* src/mod_roster_odbc.erl: Likewise (The ODBC/relational support has
not yet been tested).
* src/mod_roster.hrl: Likewise.
* src/mod_offline.erl: Likewise.
* src/mod_offline_odbc.erl: Likewise.
* odbc/pg.sql: Likewise.
* odbc/mysql.sql: Likewise.
2006-05-22 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_sm.erl: The max_user_sessions has been moved to
host configuration.
* src/ejabberd.cfg.example: Likewise.
* doc/guide.tex: Likewise.
2006-05-21 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_sm.erl: An option to limit the number of opened sessions
for a given user have been added. As a default, a given user can only
log in 10 times with different resources. After that, new connections
replace the older ones.
* src/ejabberd.cfg.example: Likewise.
* doc/guide.tex: Likewise.
2006-05-15 Mickael Remond <mickael.remond@process-one.net>
* src/web/ejabberd_http_poll.erl: Timeout disconnection were not
properly handled after "active once" migration. This is now fixed.
2006-05-07 Mickael Remond <mickael.remond@process-one.net>
* src/mod_configure.erl: Unknown tables or tables from now unused
modules are ignored during restore. The restore can now be performed in
such case.
* src/web/ejabberd_web_admin.erl: Likewise.
* src/ejabberd_admin.erl: Code refactoring. Common Mnesia database
restore function.
* src/ejabberd_ctl.erl: Now prints a user-friendly when trying to
restore a backup from a non-existent file.
* src/ejabberd_ctl.erl: Added a way to delete the older message
from the offline message table. This function is only available if
offline message queue is stored in Mnesia internal database. The
function delete_old_messages is not available in mod_offline_odbc.
2006-05-01 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_ctl.erl: Unknown tables or tables from now unused
modules are ignored during restore. The restore can now be performed in
such case.
2006-04-28 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd.hrl: release 1.1.1
* doc/version.tex: Likewise
* doc/guide.tex: Likewise
2006-04-28 Alexey Shchepin <alexey@sevcom.net>
* src/cyrsasl.erl: Bugfix: anonymous authentication was always
enabled
2006-04-27 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_ctl.erl: The status now only returns "started" if
ejabberd is started and ready to accept requests.
2006-04-24 Alexey Shchepin <alexey@sevcom.net>
* doc/guide.tex: Added a documentation for max_stanza_size option
2006-04-24 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd.hrl: Updated for version 1.1.0.
2006-04-23 Alexey Shchepin <alexey@sevcom.net>
* src/configure: Removed '==' bashism (thanks to Magnus Henoch)
* src/aclocal.m4: Likewise
* src/ejabberd_c2s.erl: Resend messages when leaving negative
presence state (thanks to Magnus Henoch)
2006-04-23 Mickael Remond <mickael.remond@process-one.net>
* src/msgs/fr.msg: Updated
* src/msgs/de.msg: Updated (thanks to Nikolaus Polak)
* src/ejabberd_sm.erl: Now delivering messages to all resources with
max equal priority when this priority is non-negative.
* src/guide.tex: Added documentation for native database configuration.
2006-04-22 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/pt-br.msg: Updated (thanks to Lucius Curado)
* src/xml_stream.erl: Support for stanza size limit (thanks to
Igor Goryachev)
* src/ejabberd_receiver.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd.cfg.example: Updated
* src/ejabberd_auth.erl: Fixed try_register/3 behaviour
2006-04-20 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd.cfg.example: Update of the example for anonymous.
Removed unnecessary parameter.
* src/ejabberd_auth_anonymous.erl: Removed unnecessary parameter /
bugfix.
* src/ejabberd_auth.erl: Bugfix: We now are forced to check is an
anonymous user is log under a given user name before trying to
register it.
* doc/guide.tex: Updated (SASL anonymous and anonymous login).
2006-04-19 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/pl.msg: Updated (thanks to Andrzej Smyk)
2006-04-13 Alexey Shchepin <alexey@sevcom.net>
* src/xml_stream.erl: Cleanup
* src/ejabberd_service.erl: Updated to use ejabberd_receiver
2006-04-12 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Minor fix
* src/ejabberd_c2s.erl: Bugfix (thanks to Sergei Golovan)
* doc/guide.tex: Updated (thanks to Sergei Golovan)
* src/msgs/nl.msg: Updated (thanks to Sander Devrieze)
2006-04-11 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/es.msg: Updated (thanks to Badlop)
* src/ejabberd.cfg.example: Updated (thanks to Badlop)
* doc/guide.tex: Updated (thanks to Badlop)
2006-04-07 Mickael Remond <mickael.remond@process-one.net>
* src/mod_pubsub/mod_pubsub.erl: Fixed pubsub root node creation
(conflict error was send on node creation)
2006-04-07 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_sm.erl: SASL Anonymous + Anonymous login support
(thanks to Mickael Remond and Magnus Henoch)
* src/ejabberd_c2s.erl: Likewise
* src/ejabberd_auth.erl: Likewise
* src/ejabberd_auth_anonymous.erl: Likewise
* src/cyrsasl.erl: Likewise
* src/cyrsasl_anonymous.erl: Likewise
* src/ejabberd.cfg.example: Likewise
2006-04-06 Alexey Shchepin <alexey@sevcom.net>
* src/expat_erl.c: Use binaries for CDATA
* src/xml.erl: Likewise
2006-04-02 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/ru.msg: Updated (thanks to Sergei Golovan)
* src/msgs/uk.msg: Likewise
* src/mod_configure.erl: Updated translation strings (thanks to
Sergei Golovan)
* src/web/ejabberd_web_admin.erl: Likewise
* src/ejabberd_s2s_in.erl: Changed a path to PKIX includes (thanks
to Sergei Golovan)
2006-04-01 Mickael Remond <mickael.remond@process-one.net>
* src/Makefile.in: Added an option to compile ejabberd with debug
print-out (make ejabberd_debug=true).
* src/ejabberd_c2s.erl: Added a new debug printout: We can now print
the XML packets send by the server.
2006-03-31 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Bugfix
2006-03-25 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Enable zlib only over TCP
2006-03-18 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Bugfix (thanks to Sergei Golovan)
2006-03-17 Alexey Shchepin <alexey@sevcom.net>
* src/mod_privacy.erl: Fixed privacy list pushing (thanks to
Sergei Golovan)
* src/ejabberd_c2s.erl: Likewise
2006-03-16 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_sm.erl: Remove unnecessary use delete_object, which can,
in some context break indexes, when used on non bag table.
2006-03-16 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Bugfix
2006-03-14 Alexey Shchepin <alexey@sevcom.net>
* src/xml_stream.erl: Added catching of gen_fsm:send_event errors
* src/ejabberd_s2s_out.erl: Better support for multiple SRV
records (thanks to Sergei Golovan)
* src/mod_muc/mod_muc_log.erl: Support for chatroom logging
(thanks to Badlop)
* src/mod_muc/mod_muc_room.erl: Likewise
* src/mod_muc/Makefile.in: Likewise
* src/mod_muc/Makefile.win32: Likewise
2006-03-11 Alexey Shchepin <alexey@sevcom.net>
* src/gen_iq_handler.erl: Added support for {queues, N} IQ handler
type
2006-03-06 Alexey Shchepin <alexey@sevcom.net>
* src/mod_muc/mod_muc_room.erl: Bugfix
2006-03-05 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Bugfix
* src/ejabberd_auth_odbc.erl: Bugfix
2006-03-04 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_logger_h.erl: reopen-log function now rename the log
file if it has not been already renamed by a logrotate process. This
change allow ejabberd administrators to rotate log files on Windows
(EJAB-52).
2006-02-27 Alexey Shchepin <alexey@sevcom.net>
* src/web/ejabberd_web_admin.erl: Added a interface for node
updating
* src/ejabberd_update.erl: Added function update_info/0
2006-02-25 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/ru.msg: Updated (thanks to Sergei Golovan)
* src/msgs/uk.msg: Likewise
* contrib/extract_translations/extract_translations.erl: Minor
update (thanks to Sergei Golovan)
* src/ejabberd_zlib/Makefile.win32: Zlib support for Windows build
(thanks to Sergei Golovan)
* src/Makefile.win32: Likewise
* src/configure.erl: Likewise
2006-02-20 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_ctl.erl: Added API for virtual host specific
commands, removed registered-users command
* src/ejabberd_auth.erl: Added processing of registered-users
command
* src/ejabberd_auth_internal.erl: Likewise
* src/ejabberd_auth_ldap.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberd_app.erl: Added inititalization of ejabberd_ctl
* src/ejabberd_sm.erl: Minor update
2006-02-18 Alexey Shchepin <alexey@sevcom.net>
* src/mod_irc/mod_irc_connection.erl: Added handling for "%", "&"
and "~" prefixes in IRC nicknames (thanks to Vladimir Kulev)
* src/mod_irc/mod_irc_connection.erl: Added /msg and /ctcp
commands, improved handling of channel topic and kick, mirc colors
now filtered, other minor improvements (thanks to Oleg V. Motienko
and Magnus Henoch)
* src/mod_configure.erl: Improved strings (thanks to Sander
Devrieze)
* src/mod_vcard.erl: Likewise
* src/mod_vcard_ldap.erl: Likewise
* src/mod_vcard_odbc.erl: Likewise
* src/web/ejabberd_web_admin.erl: Likewise
2006-02-15 Alexey Shchepin <alexey@sevcom.net>
* src/mod_service_log.erl: Bugfix (thanks to Badlop)
* src/jd2ejd.erl: Use mod_vcard_odbc if it is loaded (thanks to
Tomasz Sterna)
2006-02-14 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_sm.erl: Added a few ejabberdctl commands
* src/ejabberd_ctl.erl: New interface for command registration
2006-02-13 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_ctl.erl: Partially rewritten
* src/ejabberd_ctl.hrl: Definitions of exit status codes moved
here
2006-02-12 Alexey Shchepin <alexey@sevcom.net>
* src/mod_roster_odbc.erl: Bugfix
2006-02-08 Alexey Shchepin <alexey@sevcom.net>
* src/odbc/ejabberd_odbc.erl: Reconnect on ODBC connection closing
2006-02-07 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_auth.erl: plugin authentication modules can now
be used in the configuration file: {auth_method, module} will now
use the module ejabberd_auth_module.erl for authentication.
2006-02-05 Alexey Shchepin <alexey@sevcom.net>
* src/mod_muc/mod_muc_room.erl: Kick non-members when room
reconfigured to be member-only or when membership is revoked in
member-only room, allowed "true" and "false" in x:data forms
(thanks to Sergei Golovan)
* src/mod_configure.erl: Added "Host" parameter to get_form and
set_form functions (thanks to Sergei Golovan)
* src/ejabberd_s2s_out.erl: Bugfix
* src/msgs/es.msg: Updated (thanks to Badlop)
* src/web/ejabberd_web.erl: Bugfix (thanks to Badlop)
* src/mod_irc/mod_irc.erl: Updated copyright dates
* src/mod_muc/mod_muc.erl: Likewise
* src/mod_pubsub/mod_pubsub.erl: Likewise
* src/mod_vcard.erl: Likewise
* src/mod_vcard_ldap.erl: Likewise
* src/mod_vcard_odbc.erl: Likewise
* src/web/ejabberd_web_admin.erl: Likewise
2006-02-03 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_auth_odbc.erl: Added ejabberd_odbc_sup to ejabberd
supervision tree
* src/web/ejabberd_http.erl: Authentication check moved to
ejabberd_web.erl
* src/web/ejabberd_web.erl: Likewise
* src/web/Makefile.in: Added ejabberd_http.hrl dependency
* src/web/ejabberd_http_poll.erl: Updated to use {active, once}
socket mode
* src/mod_irc/mod_irc.erl: Updated to use gen_server behaviour and
ejabberd supervision tree
* src/mod_irc/mod_irc_connection.erl: Likewise
2006-02-02 Mickael Remond <mickael.remond@process-one.net>
* src/configure.ac: --prefix option can now override the default
install dir
* src/configure: Likewise
* src/Makefile.in: Likewise
2006-02-02 Alexey Shchepin <alexey@sevcom.net>
* src/mod_pubsub/mod_pubsub.erl: Updated to use gen_server
behaviour and ejabberd supervision tree
* src/mod_echo.erl: Likewise
2006-02-01 Alexey Shchepin <alexey@sevcom.net>
* src/mod_muc/mod_muc.erl: Added a supervisor for conference room
processes
* src/mod_muc/mod_muc_room.erl: Likewise
2006-01-29 Alexey Shchepin <alexey@sevcom.net>
* src/odbc/pg.sql: Fixed syntax error
2006-01-28 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_router.erl: Updated to use gen_server behaviour
* src/ejabberd_sm.erl: Likewise
* src/ejabberd_s2s.erl: Likewise
* src/gen_iq_handler.erl: Likewise
* src/ejabberd_sup.erl: Added supervisor for ejabberd_receiver
* src/ejabberd_receiver.erl: Updated
2006-01-27 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_update.erl: Support for run-time ejabberd updating
(not completed)
* src/ejabberd_c2s.erl: Added 'update_info' module attribute for
testing ejabberd_update
2006-01-25 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Remove top-level xmlns from incoming
stanzas
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_service.erl: Likewise
* src/ejabberd_c2s.erl: Better error handling for stream
compression support (thanks to Sergei Golovan)
2006-01-24 Alexey Shchepin <alexey@sevcom.net>
* src/mod_roster_odbc.erl: Bugfix
2006-01-23 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_sm.erl: Partially rewritten to work more
efficiently and avoid race conditions
* src/ejabberd_c2s.erl: Likewise
2006-01-21 Alexey Shchepin <alexey@sevcom.net>
* src/mod_irc/mod_irc_connection.erl: Cleanup
2006-01-20 Mickael Remond <mickael.remond@process-one.net>
* src/ejabberd_receiver.erl: Added new debugging trace: It is now
possible to dump the XML stream received from a client (usefull for
client debugging).
2006-01-19 Alexey Shchepin <alexey@sevcom.net>
* src/aclocal.m4: Updated for zlib support
* src/configure.ac: Likewise
* src/mod_muc/mod_muc_room.erl: Weakened presence filtering, added
warning in non-anonymous rooms, room destroying updated to latest
JEP-0045, added a number of occupants and room name in room's
disco#info reply, miscellaneous internal changes (thanks to Sergei
Golovan)
* src/mod_muc/mod_muc.erl: Better support for nick unregistration
(thanks to Sergei Golovan)
* src/ejabberd_zlib/ejabberd_zlib.erl: Zlib support (thanks to
Sergei Golovan)
* src/ejabberd_zlib/ejabberd_zlib_drv.c: Likewise
* src/ejabberd_zlib/Makefile.in: Likewise
* src/ejabberd_c2s.erl: Stream compression support (JEP-0138)
* src/ejabberd_receiver.erl: Likewise
* src/mod_disco.erl: Don't split node name before calling hooks
(thanks to Sergei Golovan)
* src/mod_configure.erl: Support for configuration using ad-hoc
commands (thanks to Sergei Golovan)
* src/mod_announce.erl: Support for sending announce messages
using ad-hoc commands (thanks to Sergei Golovan)
* src/mod_adhoc.erl: Ad-hoc support (JEP-0050) (thanks to Magnus
Henoch)
* src/adhoc.erl: Likewise
* src/adhoc.hrl: Likewise
* src/jlib.hrl: Updated (thanks to Sergei Golovan)
* src/gen_mod.erl: Added function is_loaded/2 (thanks to Sergei
Golovan)
* src/ejabberd_service.erl: Changed error message on handshake
error (thanks to Sergei Golovan)
* src/ejabberd.cfg.example: Updated (thanks to Sergei Golovan)
2006-01-13 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/ejabberd_odbc.erl: underscore and percent are now only
escaped in like queries. MySQL where not escaping those escaped
characters in other context.
* src/mod_vcard_odbc.erl: likewise.
* src/odbc/mysql.sql: Fixed MySQL database creation script: Was
not properly working with all MySQL version.
* src/odbc/ejabberd_odbc.erl: Added a way to retry database
connection connect for 5 minutes when the connection is lost. No
further connection is retry after.
* src/odbc/ejabberd_odbc_sup.erl: likewise.
2006-01-13 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_service.erl: Bugfix
* src/ejabberd_receiver.erl: Rewritten to use {active, once} mode
for socket
* src/ejabberd_c2s.erl: Update
* src/ejabberd_listener.erl: Likewise
* src/ejabberd_s2s_in.erl: Likewise
* src/ejabberd_s2s_out.erl: Likewise
* src/ejabberd_service.erl: Likewise
* src/shaper.erl: Likewise
* src/tls/tls.erl: Likewise
* src/web/ejabberd_http.erl: Likewise
2006-01-02 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/ejabberd_odbc.erl: Native MySQL support
2005-12-24 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_logger_h.erl: Speed optimizations
2005-12-22 Alexey Shchepin <alexey@sevcom.net>
* src/Makefile.in: Clean results of ASN.1 compiler (thanks to
Sergei Golovan)
* src/win32/ejabberd.nsh: Removed (thanks to Sergei Golovan)
2005-12-22 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/ejabberd_odbc.erl: Added error message on ODBC
connection to help ODBC configuration troubleshooting.
2005-12-22 Alexey Shchepin <alexey@sevcom.net>
* src/Makefile.win32: Updated (thanks to Sergei Golovan)
* src/configure.erl: Likewise
* src/win32/CheckReqs.ini: Likewise
* src/win32/CheckReqs1.ini: Likewise
* src/win32/CheckReqs1H.ini: Likewise
* src/win32/ejabberd.nsh: Likewise
* src/win32/ejabberd.nsi: Likewise
* src/odbc/ejabberd_odbc.erl: Bugfix
2005-12-21 Mickael Remond <mickael.remond@process-one.net>
* src/odbc/mysql.sql: Database description for MySQL Database 4.1
(Max version, with Innodb)
* src/ejabberd_auth_odbc.erl: MySQL ODBC support
* src/mod_offline_odbc.erl: Likewise
* src/mod_vcard_odbc.erl: Likewsie
* src/mod_roster_odbc.erl: Likewise
* src/odbc/ejabberd_odbc.erl: Likewise
* src/mod_last_odbc.erl: Likewise
2005-12-16 Mickael Remond <mickael.remond@process-one.net>
* doc/Makefile: Added helper to generate the docs (Thanks to Sander
Devrieze)
* doc/guide.tex: Added ejabberd 1.0.0 release notes.
* doc/dev.tex: Code clean-up (Thanks to Sander Devrieze)
2005-12-16 Alexey Shchepin <alexey@sevcom.net>
* doc/features.tex: Added feature sheet (Thanks to Sander
Devrieze)
2005-12-14 Alexey Shchepin <alexey@sevcom.net>
* src/mod_irc/iconv_erl.c: Bugfix (thanks to Tomas Janousek)
2005-12-13 Alexey Shchepin <alexey@sevcom.net>
* src/win32/ejabberd.nsi: Updated (thanks to Maxim Ryazanov)
* src/ejabberd.hrl: Updated version
* src/ejabberd.app: Likewise
* doc/version.tex: Likewise
@@ -1750,7 +2634,7 @@
2003-11-27 Alexey Shchepin <alexey@sevcom.net>
* examples/mtr/ejabberd.cfg: Updatted (thanks to Marshall T. Rose)
* examples/mtr/ejabberd.cfg: Updated (thanks to Marshall T. Rose)
* src/ejabberd_auth.erl: LDAP attribute that holds user ID now
configurable
-3
View File
@@ -1,7 +1,5 @@
Win32 build: Make it possible to compile with +debug_info flag.
mod_muc logging
admin interface
users management
statistics about each user
@@ -10,7 +8,6 @@ admin interface
S2S:
check "id" attributes in db:verify packets
more correctly work with SRV DNS records (priority, weight, etc...)
make roster set to work in one transaction
add traffic shapers to c2s connection before authentification
more traffic shapers
@@ -108,7 +108,7 @@ parse_form(Dir, File, Form, Used) ->
ok
end.
process_string(Dir, File, Str, Used) ->
process_string(_Dir, File, Str, Used) ->
case {ets:lookup(translations, Str), Used} of
{[{_Key, _Trans}], unused} ->
ets:delete(translations, Str);
+39 -4
View File
@@ -1,11 +1,46 @@
# $Id$
all: guide.html dev.html
all: release pdf html
release:
@echo "Notes for the releaser:"
@echo "* Do not forget to add a link to the release notes in guide.tex"
@echo "* Do not forget to change the version number in version.tex"
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
@read -p "press any key to continue"
html: guide.html dev.html features.html
pdf: guide.pdf features.pdf
clean:
rm -f *.aux
rm -f *.haux
rm -f *.html
rm -f *.htoc
rm -f *.idx
rm -f *.ilg
rm -f *.ind
rm -f *.log
rm -f *.out
rm -f *.pdf
rm -f *.toc
guide.html: guide.tex
hevea -charset ISO8859-1 guide.tex
hevea -fix -noiso -pedantic guide.tex
dev.html: dev.tex
hevea -charset ISO8859-1 dev.tex
hevea -fix -noiso -pedantic dev.tex
features.html: features.tex
hevea -fix -noiso -pedantic features.tex
guide.pdf: guide.tex
pdflatex guide.tex
pdflatex guide.tex
pdflatex guide.tex
makeindex guide.idx
pdflatex guide.tex
features.pdf: features.tex
pdflatex features.tex
+201 -135
View File
@@ -1,99 +1,184 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD><TITLE>Ejabberd Developers Guide</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=ISO8859-1">
<META name="GENERATOR" content="hevea 1.07">
<HEAD>
<TITLE>Ejabberd 1.1.2 Developers Guide</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<META name="GENERATOR" content="hevea 1.08">
<STYLE type="text/css">
.toc{list-style:none;}
.title{margin:auto;text-align:center}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
.part{margin:auto;text-align:center}
</STYLE>
</HEAD>
<BODY >
<!--HEVEA command line is: /usr/bin/hevea -charset ISO8859-1 dev.tex -->
<!--HEVEA command line is: hevea -fix -noiso -pedantic dev.tex -->
<!--HTMLHEAD-->
<!--ENDHTML-->
<!--PREFIX <ARG ></ARG>-->
<!--CUT DEF section 1 -->
<BR>
<BR>
<A NAME="sec:titlepage"></A>
<H1 ALIGN=center>Ejabberd Developers Guide</H1>
<H3 ALIGN=center>Alexey Shchepin<BR>
<TABLE CLASS="title">
<TR><TD>
<H1 CLASS="titlemain">Ejabberd 1.1.2 Developers Guide</H1>
<H3 CLASS="titlerest">Alexey Shchepin<BR>
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3>
<H3 ALIGN=center>August 21, 2005</H3><DIV ALIGN=center>
<IMG SRC="logo.png">
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD>
</TR></TABLE><BR>
<BR>
<DIV CLASS="center">
<IMG SRC="logo.png" ALT="logo.png">
</DIV><BR>
<BR>
<BR>
</DIV>
<BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup &ndash;
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE>
<!--TOC section Contents-->
<!--TOC section Table of Contents-->
<H2 CLASS="section">Contents</H2><!--SEC END -->
<H2>Table of Contents</H2><!--SEC END -->
<UL><LI>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc1">1&nbsp;&nbsp;Introduction</A>
<UL><LI>
<A HREF="#htoc2">1.1&nbsp;&nbsp;How it works</A>
<UL><LI>
<A HREF="#htoc3">1.1.1&nbsp;&nbsp;Router</A>
<LI><A HREF="#htoc4">1.1.2&nbsp;&nbsp;Local Router</A>
<LI><A HREF="#htoc5">1.1.3&nbsp;&nbsp;Session Manager</A>
<LI><A HREF="#htoc6">1.1.4&nbsp;&nbsp;S2S Manager</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc2">1.1&nbsp;&nbsp;Key Features</A>
<LI CLASS="li-toc"><A HREF="#htoc3">1.2&nbsp;&nbsp;Additional Features</A>
</UL>
<LI CLASS="li-toc"><A HREF="#htoc4">2&nbsp;&nbsp;How it Works</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc5">2.1&nbsp;&nbsp;Router</A>
<LI CLASS="li-toc"><A HREF="#htoc6">2.2&nbsp;&nbsp;Local Router</A>
<LI CLASS="li-toc"><A HREF="#htoc7">2.3&nbsp;&nbsp;Session Manager</A>
<LI CLASS="li-toc"><A HREF="#htoc8">2.4&nbsp;&nbsp;S2S Manager</A>
</UL>
<LI><A HREF="#htoc7">2&nbsp;&nbsp;XML representation</A>
<LI><A HREF="#htoc8">3&nbsp;&nbsp;Module <TT>xml</TT></A>
<LI><A HREF="#htoc9">4&nbsp;&nbsp;Module <TT>xml_stream</TT></A>
<LI><A HREF="#htoc10">5&nbsp;&nbsp;<TT>ejabberd</TT> modules</A>
<UL><LI>
<A HREF="#htoc11">5.1&nbsp;&nbsp;<CODE>gen_mod</CODE> behaviour</A>
<LI><A HREF="#htoc12">5.2&nbsp;&nbsp;Module <CODE>gen_iq_handler</CODE></A>
<LI><A HREF="#htoc13">5.3&nbsp;&nbsp;Services</A>
<LI CLASS="li-toc"><A HREF="#htoc9">3&nbsp;&nbsp;XML Representation</A>
<LI CLASS="li-toc"><A HREF="#htoc10">4&nbsp;&nbsp;Module <TT>xml</TT></A>
<LI CLASS="li-toc"><A HREF="#htoc11">5&nbsp;&nbsp;Module <TT>xml_stream</TT></A>
<LI CLASS="li-toc"><A HREF="#htoc12">6&nbsp;&nbsp;Modules</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc13">6.1&nbsp;&nbsp;Module gen_iq_handler</A>
<LI CLASS="li-toc"><A HREF="#htoc14">6.2&nbsp;&nbsp;Services</A>
</UL>
</UL>
<!--TOC section Introduction-->
<H2><A NAME="htoc1">1</A>&nbsp;&nbsp;Introduction</H2><!--SEC END -->
<H2 CLASS="section"><A NAME="htoc1">1</A>&nbsp;&nbsp;<A NAME="intro">Introduction</A></H2><!--SEC END -->
<A NAME="sec:intro"></A>
<TT>ejabberd</TT> is a Free and Open Source fault-tolerant distributed Jabber
server. It is written mostly in Erlang.<BR>
<TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.<BR>
<BR>
The main features of <TT>ejabberd</TT> are:
<UL><LI>
Works on most of popular platforms: *nix (tested on Linux, FreeBSD and
NetBSD) and Win32
<LI>Distributed: You can run <TT>ejabberd</TT> on a cluster of machines to let all of
them serve one Jabber domain.
<LI>Fault-tolerance: You can setup an <TT>ejabberd</TT> cluster so that all the
information required for a properly working service will be stored
permanently on more than one node. This means that if one of the nodes
crashes, then the others will continue working without disruption.
You can also add or replace nodes ``on the fly''.
<LI>Support for virtual hosting
<LI>Built-in <A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User Chat</A> service
<LI>Built-in IRC transport
<LI>Built-in <A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A> service
<LI>Built-in Jabber Users Directory service based on users vCards
<LI>Built-in web-based administration interface
<LI>Built-in <A HREF="http://www.jabber.org/jeps/jep-0025.html">HTTP Polling</A> service
<LI>SSL support
<LI>Support for LDAP authentication
<LI>Ability to interface with external components (JIT, MSN-t, Yahoo-t, etc.)
<LI>Migration from jabberd14 is possible
<LI>Mostly XMPP-compliant
<LI>Support for <A HREF="http://www.jabber.org/jeps/jep-0030.html">Service Discovery</A>.
<LI>Support for <A HREF="http://www.jabber.org/jeps/jep-0039.html">Statistics Gathering</A>.
<LI>Support for <TT>xml:lang</TT>
</UL>
<!--TOC subsection How it works-->
<TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.<BR>
<BR>
<TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.<BR>
<BR>
<TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.<BR>
<BR>
<!--TOC subsection Key Features-->
<H3><A NAME="htoc2">1.1</A>&nbsp;&nbsp;How it works</H3><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc2">1.1</A>&nbsp;&nbsp;<A NAME="keyfeatures">Key Features</A></H3><!--SEC END -->
<A NAME="sec:keyfeatures"></A>
<TT>ejabberd</TT> is:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.<BR>
<BR>
<LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.<BR>
<BR>
<LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.<BR>
<BR>
<LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Comprehensive documentation.
<LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows.
<LI CLASS="li-itemize">Web interface for administration tasks.
<LI CLASS="li-itemize">Shared Roster Groups.
<LI CLASS="li-itemize">Command line administration tool.
<LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
<LI CLASS="li-itemize">Capability to send announce messages.
</UL><BR>
<BR>
<LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated in 12 languages.
<LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</UL><BR>
<BR>
<LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
<LI CLASS="li-itemize">XML-based protocol.
<LI CLASS="li-itemize"><A HREF="http://ejabberd.jabber.ru/protocols">Many JEPs supported</A>.
</UL></UL>
<!--TOC subsection Additional Features-->
<H3 CLASS="subsection"><A NAME="htoc3">1.2</A>&nbsp;&nbsp;<A NAME="addfeatures">Additional Features</A></H3><!--SEC END -->
<A NAME="sec:addfeatures"></A>
Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
Load only the modules you want.
<LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</UL>
<LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
<LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
<LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
</UL>
<LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
Native MySQL support.
<LI CLASS="li-itemize">Native PostgreSQL support.
<LI CLASS="li-itemize">Mnesia.
<LI CLASS="li-itemize">ODBC data storage support.
<LI CLASS="li-itemize">Microsoft SQL Server support.
</UL>
<LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
LDAP and ODBC.
<LI CLASS="li-itemize">External Authentication script.
<LI CLASS="li-itemize">Internal Authentication.
</UL>
<LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
Compressing XML streams with Stream Compression (<A HREF="http://www.jabber.org/jeps/jep-0138.html">JEP-0138</A>).
<LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
<LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.jabber.org/jeps/jep-0039.html">JEP-0039</A>).
<LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User Chat</A> module with logging.
<LI CLASS="li-itemize">Users Directory based on users vCards.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A> component.
<LI CLASS="li-itemize">Support for virtual hosting.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0025.html">HTTP Polling</A> service.
<LI CLASS="li-itemize">IRC transport.
</UL>
</UL>
<!--TOC section How it Works-->
<H2 CLASS="section"><A NAME="htoc4">2</A>&nbsp;&nbsp;How it Works</H2><!--SEC END -->
<A NAME="sec:howitworks"></A>
A Jabber domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
@@ -105,15 +190,15 @@ needed because all nodes exchange information about connected users, S2S
connections, registered services, etc...<BR>
<BR>
Each <TT>ejabberd</TT> node have following modules:
<UL><LI>
<UL CLASS="itemize"><LI CLASS="li-itemize">
router;
<LI>local router.
<LI>session manager;
<LI>S2S manager;
<LI CLASS="li-itemize">local router.
<LI CLASS="li-itemize">session manager;
<LI CLASS="li-itemize">S2S manager;
</UL>
<!--TOC subsubsection Router-->
<!--TOC subsection Router-->
<H4><A NAME="htoc3">1.1.1</A>&nbsp;&nbsp;Router</H4><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc5">2.1</A>&nbsp;&nbsp;Router</H3><!--SEC END -->
This module is the main router of Jabber packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
@@ -123,26 +208,26 @@ searches in global table, and is routed to the appropriate <TT>ejabberd</TT> nod
process. If it does not exists in either tables, then it sent to the S2S
manager.<BR>
<BR>
<!--TOC subsubsection Local Router-->
<!--TOC subsection Local Router-->
<H4><A NAME="htoc4">1.1.2</A>&nbsp;&nbsp;Local Router</H4><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc6">2.2</A>&nbsp;&nbsp;Local Router</H3><!--SEC END -->
This module routes packets which have a destination domain equal to this server
name. If destination JID has a non-empty user part, then it routed to the
session manager, else it is processed depending on it's content.<BR>
<BR>
<!--TOC subsubsection Session Manager-->
<!--TOC subsection Session Manager-->
<H4><A NAME="htoc5">1.1.3</A>&nbsp;&nbsp;Session Manager</H4><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc7">2.3</A>&nbsp;&nbsp;Session Manager</H3><!--SEC END -->
This module routes packets to local users. It searches for what user resource
packet must be sended via presence table. If this resource is connected to
this node, it is routed to C2S process, if it connected via another node, then
the packet is sent to session manager on that node.<BR>
<BR>
<!--TOC subsubsection S2S Manager-->
<!--TOC subsection S2S Manager-->
<H4><A NAME="htoc6">1.1.4</A>&nbsp;&nbsp;S2S Manager</H4><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc8">2.4</A>&nbsp;&nbsp;S2S Manager</H3><!--SEC END -->
This module routes packets to other Jabber servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
@@ -151,13 +236,13 @@ routes the packet to S2S manager on that node, if it is open on this node, then
it is routed to the process that serves this connection, and if a connection
does not exist, then it is opened and registered.<BR>
<BR>
<!--TOC section XML representation-->
<!--TOC section XML Representation-->
<H2><A NAME="htoc7">2</A>&nbsp;&nbsp;XML representation</H2><!--SEC END -->
<H2 CLASS="section"><A NAME="htoc9">3</A>&nbsp;&nbsp;XML Representation</H2><!--SEC END -->
<A NAME="sec:xmlrepr"></A>
Each XML stanza is represented as the following tuple:
<PRE>
<PRE CLASS="verbatim">
XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
Name = string()
Attrs = [Attr]
@@ -167,12 +252,12 @@ XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
ElementOrCDATA = XMLElement | CDATA
CDATA = {xmlcdata, string()}
</PRE>E.&nbsp;g. this stanza:
<PRE>
<PRE CLASS="verbatim">
&lt;message to='test@conference.example.org' type='groupchat'&gt;
&lt;body&gt;test&lt;/body&gt;
&lt;/message&gt;
</PRE>is represented as the following structure:
<PRE>
<PRE CLASS="verbatim">
{xmlelement, "message",
[{"to", "test@conference.example.org"},
{"type", "groupchat"}],
@@ -182,29 +267,28 @@ XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
</PRE>
<!--TOC section Module <TT>xml</TT>-->
<H2><A NAME="htoc8">3</A>&nbsp;&nbsp;Module <TT>xml</TT></H2><!--SEC END -->
<H2 CLASS="section"><A NAME="htoc10">4</A>&nbsp;&nbsp;Module <TT>xml</TT></H2><!--SEC END -->
<A NAME="sec:xmlmod"></A>
<DL COMPACT=compact><DT>
<CODE><B>element_to_string(El) -&gt; string()</B></CODE><DD>
<PRE>
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>element_to_string(El) -&gt; string()</CODE>
<PRE CLASS="verbatim">
El = XMLElement
</PRE>Returns string representation of XML stanza <TT>El</TT>.<BR>
<BR>
<DT><CODE><B>crypt(S) -&gt; string()</B></CODE><DD>
<PRE>
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>crypt(S) -&gt; string()</CODE>
<PRE CLASS="verbatim">
S = string()
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
characters.<BR>
<BR>
<DT><CODE><B>remove_cdata(ECList) -&gt; EList</B></CODE><DD>
<PRE>
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -&gt; EList</CODE>
<PRE CLASS="verbatim">
ECList = [ElementOrCDATA]
EList = [XMLElement]
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.<BR>
<BR>
<DT><CODE><B>get_path_s(El, Path) -&gt; Res</B></CODE><DD>
<PRE>
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -&gt; Res</CODE>
<PRE CLASS="verbatim">
El = XMLElement
Path = [PathItem]
PathItem = PathElem | PathAttr | PathCDATA
@@ -215,17 +299,16 @@ Name = string()
Res = string() | XMLElement
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
consider elements of <TT>Path</TT>. Each element is one of:
<DL COMPACT=compact><DT>
<CODE><B>{elem, Name}</B></CODE><DD> <TT>Name</TT> is name of subelement of
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
<TT>El</TT>, if such element exists, then this element considered in
following steps, else returns empty string.
<DT><CODE><B>{attr, Name}</B></CODE><DD> If <TT>El</TT> have attribute <TT>Name</TT>, then
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
returns value of this attribute, else returns empty string.
<DT><CODE><B>cdata</B></CODE><DD> Returns CDATA of <TT>El</TT>.
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
</DL><BR>
<BR>
<DT><B>TODO:</B><DD>
<PRE>
<DT CLASS="dt-description"><DD CLASS="dd-description">TODO:
<PRE CLASS="verbatim">
get_cdata/1, get_tag_cdata/1
get_attr/2, get_attr_s/2
get_tag_attr/2, get_tag_attr_s/2
@@ -233,41 +316,32 @@ Res = string() | XMLElement
</PRE></DL>
<!--TOC section Module <TT>xml_stream</TT>-->
<H2><A NAME="htoc9">4</A>&nbsp;&nbsp;Module <TT>xml_stream</TT></H2><!--SEC END -->
<H2 CLASS="section"><A NAME="htoc11">5</A>&nbsp;&nbsp;Module <TT>xml_stream</TT></H2><!--SEC END -->
<A NAME="sec:xmlstreammod"></A>
<DL COMPACT=compact><DT>
<CODE><B>parse_element(Str) -&gt; XMLElement | {error, Err}</B></CODE><DD>
<PRE>
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>parse_element(Str) -&gt; XMLElement | {error, Err}</CODE>
<PRE CLASS="verbatim">
Str = string()
Err = term()
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
tuple.
</DL>
<!--TOC section <TT>ejabberd</TT> modules-->
<!--TOC section Modules-->
<H2><A NAME="htoc10">5</A>&nbsp;&nbsp;<TT>ejabberd</TT> modules</H2><!--SEC END -->
<H2 CLASS="section"><A NAME="htoc12">6</A>&nbsp;&nbsp;Modules</H2><!--SEC END -->
<A NAME="sec:emods"></A>
<!--TOC subsection <CODE>gen_mod</CODE> behaviour-->
<!--TOC subsection Module gen_iq_handler-->
<H3><A NAME="htoc11">5.1</A>&nbsp;&nbsp;<CODE>gen_mod</CODE> behaviour</H3><!--SEC END -->
<A NAME="sec:genmod"></A>
TBD<BR>
<BR>
<!--TOC subsection Module <CODE>gen_iq_handler</CODE>-->
<H3><A NAME="htoc12">5.2</A>&nbsp;&nbsp;Module <CODE>gen_iq_handler</CODE></H3><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc13">6.1</A>&nbsp;&nbsp;Module gen_iq_handler</H3><!--SEC END -->
<A NAME="sec:geniqhandl"></A>
The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
of particular XML namespaces that addressed to server or to users bare JIDs.<BR>
<BR>
In this module the following functions are defined:
<DL COMPACT=compact><DT>
<CODE><B>add_iq_handler(Component, Host, NS, Module, Function, Type)</B></CODE><DD>
<PRE>
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
<PRE CLASS="verbatim">
Component = Module = Function = atom()
Host = NS = string()
Type = no_queue | one_queue | parallel
@@ -275,24 +349,22 @@ Type = no_queue | one_queue | parallel
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
two components defined:
<DL COMPACT=compact><DT>
<CODE><B>ejabberd_local</B></CODE><DD> Handles packets that addressed to server JID;
<DT><CODE><B>ejabberd_sm</B></CODE><DD> Handles packets that addressed to users bare JIDs.
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
</DL>
<DT><CODE><B>remove_iq_handler(Component, Host, NS)</B></CODE><DD>
<PRE>
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
<PRE CLASS="verbatim">
Component = atom()
Host = NS = string()
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
<CODE>Component</CODE>.
</DL>
Handler function must have the following type:
<DL COMPACT=compact><DT>
<CODE><B>Module:Function(From, To, IQ)</B></CODE><DD>
<PRE>
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
<PRE CLASS="verbatim">
From = To = jid()
</PRE></DL>
<PRE>
<PRE CLASS="verbatim">
-module(mod_cputime).
-behaviour(gen_mod).
@@ -330,13 +402,10 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) -&gt;
</PRE>
<!--TOC subsection Services-->
<H3><A NAME="htoc13">5.3</A>&nbsp;&nbsp;Services</H3><!--SEC END -->
<H3 CLASS="subsection"><A NAME="htoc14">6.2</A>&nbsp;&nbsp;Services</H3><!--SEC END -->
<A NAME="sec:services"></A>
TBD<BR>
<BR>
TODO: use <CODE>proc_lib</CODE>
<PRE>
<PRE CLASS="verbatim">
-module(mod_echo).
-behaviour(gen_mod).
@@ -375,9 +444,6 @@ stop(Host) -&gt;
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2>
<BLOCKQUOTE><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.
</EM></BLOCKQUOTE>
</BODY>
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>
+91 -87
View File
@@ -1,14 +1,20 @@
\documentclass[10pt]{article}
\documentclass[a4paper,10pt]{article}
%% Packages
\usepackage{graphics}
\usepackage{hevea}
\usepackage{makeidx}
\usepackage{verbatim}
%% Index
\makeindex
% Remove the index anchors from the HTML version to save size and bandwith.
\newcommand{\ind}[1]{\begin{latexonly}\index{#1}\end{latexonly}}
%% Images
\newcommand{\logoscale}{0.7}
\newcommand{\imgscale}{0.58}
\newcommand{\insimg}[1]{\insscaleimg{\imgscale}{#1}}
\newcommand{\insscaleimg}[2]{
\imgsrc{#2}{}
\begin{latexonly}
@@ -16,83 +22,81 @@
\end{latexonly}
}
%% Various
\newcommand{\ns}[1]{\texttt{#1}}
\newcommand{\ejabberd}{\texttt{ejabberd}}
\newcommand{\Jabber}{Jabber}
\newcommand{\modregister}{\texttt{mod\_register}}
\newcommand{\modroster}{\texttt{mod\_roster}}
\newcommand{\modconfigure}{\texttt{mod\_configure}}
\newcommand{\moddisco}{\texttt{mod\_disco}}
\newcommand{\modstats}{\texttt{mod\_stats}}
\newcommand{\modvcard}{\texttt{mod\_vcard}}
\newcommand{\modoffline}{\texttt{mod\_offline}}
\newcommand{\modecho}{\texttt{mod\_echo}}
\newcommand{\modprivate}{\texttt{mod\_private}}
\newcommand{\modtime}{\texttt{mod\_time}}
\newcommand{\modversion}{\texttt{mod\_version}}
%% Modules
\newcommand{\module}[1]{\texttt{#1}}
\newcommand{\modadhoc}{\module{mod\_adhoc}}
\newcommand{\modannounce}{\module{mod\_announce}}
\newcommand{\modconfigure}{\module{mod\_configure}}
\newcommand{\moddisco}{\module{mod\_disco}}
\newcommand{\modecho}{\module{mod\_echo}}
\newcommand{\modirc}{\module{mod\_irc}}
\newcommand{\modlast}{\module{mod\_last}}
\newcommand{\modlastodbc}{\module{mod\_last\_odbc}}
\newcommand{\modmuc}{\module{mod\_muc}}
\newcommand{\modmuclog}{\module{mod\_muc\_log}}
\newcommand{\modoffline}{\module{mod\_offline}}
\newcommand{\modofflineodbc}{\module{mod\_offline\_odbc}}
\newcommand{\modprivacy}{\module{mod\_privacy}}
\newcommand{\modprivate}{\module{mod\_private}}
\newcommand{\modpubsub}{\module{mod\_pubsub}}
\newcommand{\modregister}{\module{mod\_register}}
\newcommand{\modroster}{\module{mod\_roster}}
\newcommand{\modrosterodbc}{\module{mod\_roster\_odbc}}
\newcommand{\modservicelog}{\module{mod\_service\_log}}
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
\newcommand{\modstats}{\module{mod\_stats}}
\newcommand{\modtime}{\module{mod\_time}}
\newcommand{\modvcard}{\module{mod\_vcard}}
\newcommand{\modvcardldap}{\module{mod\_vcard\_ldap}}
\newcommand{\modvcardodbc}{\module{mod\_vcard\_odbc}}
\newcommand{\modversion}{\module{mod\_version}}
\newcommand{\tjepref}[2]{\footahref{http://www.jabber.org/jeps/jep-#1.html}{#2}}
\newcommand{\jepref}[1]{\tjepref{#1}{JEP-#1}}
%\setcounter{tocdepth}{3}
\title{Ejabberd Developers Guide}
%% Title page
\include{version}
\title{Ejabberd \version\ Developers Guide}
\author{Alexey Shchepin \\
\ahrefurl{mailto:alexey@sevcom.net} \\
\ahrefurl{xmpp:aleksey@jabber.ru}}
\date{August 21, 2005}
%% Options
\newcommand{\marking}[1]{#1} % Marking disabled
\newcommand{\quoting}[2][yozhik]{} % Quotes disabled
\newcommand{\new}{\begin{latexonly}\marginpar{\textsc{new}}\end{latexonly}} % Highlight new features
\newcommand{\improved}{\begin{latexonly}\marginpar{\textsc{improved}}\end{latexonly}} % Highlight improved features
\newcommand{\moreinfo}[1]{} % Hide details
%% Footnotes
\newcommand{\tjepref}[2]{\footahref{http://www.jabber.org/jeps/jep-#1.html}{#2}}
\newcommand{\jepref}[1]{\tjepref{#1}{JEP-#1}}
\begin{document}
\label{sec:titlepage}
\begin{titlepage}
\maketitle{}
{\centering
\insscaleimg{\logoscale}{logo.png}
\begin{center}
{\insscaleimg{\logoscale}{logo.png}
\par
}
\end{center}
\begin{quotation}\textit{I can thoroughly recommend ejabberd for ease of setup --
Kevin Smith, Current maintainer of the Psi project}\end{quotation}
\end{titlepage}
%\newpage
\tableofcontents{}
\newpage
\section{Introduction}
\label{sec:intro}
% Input introduction.tex
\input{introduction}
\ejabberd{} is a Free and Open Source fault-tolerant distributed \Jabber{}
server. It is written mostly in Erlang.
The main features of \ejabberd{} are:
\begin{itemize}
\item Works on most of popular platforms: *nix (tested on Linux, FreeBSD and
NetBSD) and Win32
\item Distributed: You can run \ejabberd{} on a cluster of machines to let all of
them serve one Jabber domain.
\item Fault-tolerance: You can setup an \ejabberd{} cluster so that all the
information required for a properly working service will be stored
permanently on more than one node. This means that if one of the nodes
crashes, then the others will continue working without disruption.
You can also add or replace nodes ``on the fly''.
\item Support for virtual hosting
\item Built-in \tjepref{0045}{Multi-User Chat} service
\item Built-in IRC transport
\item Built-in \tjepref{0060}{Publish-Subscribe} service
\item Built-in Jabber Users Directory service based on users vCards
\item Built-in web-based administration interface
\item Built-in \tjepref{0025}{HTTP Polling} service
\item SSL support
\item Support for LDAP authentication
\item Ability to interface with external components (JIT, MSN-t, Yahoo-t, etc.)
\item Migration from jabberd14 is possible
\item Mostly XMPP-compliant
\item Support for \tjepref{0030}{Service Discovery}.
\item Support for \tjepref{0039}{Statistics Gathering}.
\item Support for \ns{xml:lang}
\end{itemize}
\subsection{How it works}
\section{How it Works}
\label{sec:howitworks}
@@ -115,7 +119,7 @@ Each \ejabberd{} node have following modules:
\end{itemize}
\subsubsection{Router}
\subsection{Router}
This module is the main router of \Jabber{} packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
@@ -126,14 +130,14 @@ process. If it does not exists in either tables, then it sent to the S2S
manager.
\subsubsection{Local Router}
\subsection{Local Router}
This module routes packets which have a destination domain equal to this server
name. If destination JID has a non-empty user part, then it routed to the
session manager, else it is processed depending on it's content.
\subsubsection{Session Manager}
\subsection{Session Manager}
This module routes packets to local users. It searches for what user resource
packet must be sended via presence table. If this resource is connected to
@@ -141,7 +145,7 @@ this node, it is routed to C2S process, if it connected via another node, then
the packet is sent to session manager on that node.
\subsubsection{S2S Manager}
\subsection{S2S Manager}
This module routes packets to other \Jabber{} servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
@@ -153,7 +157,7 @@ does not exist, then it is opened and registered.
\section{XML representation}
\section{XML Representation}
\label{sec:xmlrepr}
Each XML stanza is represented as the following tuple:
@@ -189,20 +193,20 @@ is represented as the following structure:
\label{sec:xmlmod}
\begin{description}
\item[\verb|element_to_string(El) -> string()|]
\item{\verb|element_to_string(El) -> string()|}
\begin{verbatim}
El = XMLElement
\end{verbatim}
Returns string representation of XML stanza \texttt{El}.
\item[\verb|crypt(S) -> string()|]
\item{\verb|crypt(S) -> string()|}
\begin{verbatim}
S = string()
\end{verbatim}
Returns string which correspond to \texttt{S} with encoded XML special
characters.
\item[\verb|remove_cdata(ECList) -> EList|]
\item{\verb|remove_cdata(ECList) -> EList|}
\begin{verbatim}
ECList = [ElementOrCDATA]
EList = [XMLElement]
@@ -211,7 +215,7 @@ EList = [XMLElement]
\item[\verb|get_path_s(El, Path) -> Res|]
\item{\verb|get_path_s(El, Path) -> Res|}
\begin{verbatim}
El = XMLElement
Path = [PathItem]
@@ -225,15 +229,15 @@ Res = string() | XMLElement
If \texttt{Path} is empty, then returns \texttt{El}. Else sequentially
consider elements of \texttt{Path}. Each element is one of:
\begin{description}
\item[\verb|{elem, Name}|] \texttt{Name} is name of subelement of
\item{\verb|{elem, Name}|} \texttt{Name} is name of subelement of
\texttt{El}, if such element exists, then this element considered in
following steps, else returns empty string.
\item[\verb|{attr, Name}|] If \texttt{El} have attribute \texttt{Name}, then
\item{\verb|{attr, Name}|} If \texttt{El} have attribute \texttt{Name}, then
returns value of this attribute, else returns empty string.
\item[\verb|cdata|] Returns CDATA of \texttt{El}.
\item{\verb|cdata|} Returns CDATA of \texttt{El}.
\end{description}
\item[TODO:]
\item{TODO:}
\begin{verbatim}
get_cdata/1, get_tag_cdata/1
get_attr/2, get_attr_s/2
@@ -247,7 +251,7 @@ Res = string() | XMLElement
\label{sec:xmlstreammod}
\begin{description}
\item[\verb!parse_element(Str) -> XMLElement | {error, Err}!]
\item{\verb!parse_element(Str) -> XMLElement | {error, Err}!}
\begin{verbatim}
Str = string()
Err = term()
@@ -257,16 +261,16 @@ Err = term()
\end{description}
\section{\ejabberd{} modules}
\section{Modules}
\label{sec:emods}
\subsection{\verb|gen_mod| behaviour}
\label{sec:genmod}
%\subsection{gen\_mod behaviour}
%\label{sec:genmod}
TBD
%TBD
\subsection{Module \verb|gen_iq_handler|}
\subsection{Module gen\_iq\_handler}
\label{sec:geniqhandl}
The module \verb|gen_iq_handler| allows to easily write handlers for IQ packets
@@ -274,7 +278,7 @@ of particular XML namespaces that addressed to server or to users bare JIDs.
In this module the following functions are defined:
\begin{description}
\item[\verb|add_iq_handler(Component, Host, NS, Module, Function, Type)|]
\item{\verb|add_iq_handler(Component, Host, NS, Module, Function, Type)|}
\begin{verbatim}
Component = Module = Function = atom()
Host = NS = string()
@@ -285,10 +289,10 @@ Type = no_queue | one_queue | parallel
\verb|Component|. Queueing discipline is \verb|Type|. There are at least
two components defined:
\begin{description}
\item[\verb|ejabberd_local|] Handles packets that addressed to server JID;
\item[\verb|ejabberd_sm|] Handles packets that addressed to users bare JIDs.
\item{\verb|ejabberd_local|} Handles packets that addressed to server JID;
\item{\verb|ejabberd_sm|} Handles packets that addressed to users bare JIDs.
\end{description}
\item[\verb|remove_iq_handler(Component, Host, NS)|]
\item{\verb|remove_iq_handler(Component, Host, NS)|}
\begin{verbatim}
Component = atom()
Host = NS = string()
@@ -299,7 +303,7 @@ Host = NS = string()
Handler function must have the following type:
\begin{description}
\item[\verb|Module:Function(From, To, IQ)|]
\item{\verb|Module:Function(From, To, IQ)|}
\begin{verbatim}
From = To = jid()
\end{verbatim}
@@ -348,10 +352,10 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
\subsection{Services}
\label{sec:services}
TBD
%TBD
TODO: use \verb|proc_lib|
%TODO: use \verb|proc_lib|
\begin{verbatim}
-module(mod_echo).
+163
View File
@@ -0,0 +1,163 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 1.1.2 Feature Sheet</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<META name="GENERATOR" content="hevea 1.08">
<STYLE type="text/css">
.toc{list-style:none;}
.title{margin:auto;text-align:center}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
.part{margin:auto;text-align:center}
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
</STYLE>
</HEAD>
<BODY >
<!--HEVEA command line is: hevea -fix -noiso -pedantic features.tex -->
<!--HTMLHEAD-->
<!--ENDHTML-->
<!--PREFIX <ARG ></ARG>-->
<!--CUT DEF section 1 -->
<BR>
<BR>
<A NAME="titlepage"></A>
<TABLE CLASS="title">
<TR><TD>
<H1 CLASS="titlemain">Ejabberd 1.1.2 Feature Sheet</H1>
<H3 CLASS="titlerest">Sander Devrieze<BR>
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD>
</TR></TABLE><BR>
<BR>
<DIV CLASS="center">
<IMG SRC="logo.png" ALT="logo.png">
<BR>
<BR>
</DIV>
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup &ndash;
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE>
<!--TOC section Introduction-->
<H2 CLASS="section"><A NAME="intro">Introduction</A></H2><!--SEC END -->
<A NAME="sec:intro"></A>
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. &mdash;
Joeri</I></FONT></BLOCKQUOTE>
<TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.<BR>
<BR>
<TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.<BR>
<BR>
<TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.<BR>
<BR>
<TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.<BR>
<BR>
<!--TOC subsection Key Features-->
<H3 CLASS="subsection"><A NAME="keyfeatures">Key Features</A></H3><!--SEC END -->
<A NAME="sec:keyfeatures"></A>
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. &mdash;
Peter Saint-Andr&eacute;, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE>
<TT>ejabberd</TT> is:
<UL CLASS="itemize"><LI CLASS="li-itemize">
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.<BR>
<BR>
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.<BR>
<BR>
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.<BR>
<BR>
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Comprehensive documentation.
<LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
<LI CLASS="li-itemize">Web interface for administration tasks.
<LI CLASS="li-itemize">Shared Roster Groups.
<LI CLASS="li-itemize">Command line administration tool.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
<LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
<LI CLASS="li-itemize">Capability to send announce messages.
</UL><BR>
<BR>
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated in 12 languages.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
<LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</UL><BR>
<BR>
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
<LI CLASS="li-itemize">XML-based protocol.
<LI CLASS="li-itemize"><A HREF="http://ejabberd.jabber.ru/protocols">Many JEPs supported</A>.
</UL></UL>
<!--TOC subsection Additional Features-->
<H3 CLASS="subsection"><A NAME="addfeatures">Additional Features</A></H3><!--SEC END -->
<A NAME="sec:addfeatures"></A>
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem &mdash;
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE>
Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
Load only the modules you want.
<LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</UL>
<LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
<LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
<LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
</UL>
<LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
Native MySQL support.
<LI CLASS="li-itemize">Native PostgreSQL support.
<LI CLASS="li-itemize">Mnesia.
<LI CLASS="li-itemize">ODBC data storage support.
<LI CLASS="li-itemize">Microsoft SQL Server support.<FONT COLOR="red"><SPAN STYLE="font-variant:small-caps">new</SPAN></FONT>
</UL>
<LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
LDAP and ODBC.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
<LI CLASS="li-itemize">External Authentication script.
<LI CLASS="li-itemize">Internal Authentication.
</UL>
<LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
Compressing XML streams with Stream Compression (<A HREF="http://www.jabber.org/jeps/jep-0138.html">JEP-0138</A>).
<LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
<LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.jabber.org/jeps/jep-0039.html">JEP-0039</A>).
<LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User Chat</A> module with logging.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
<LI CLASS="li-itemize">Users Directory based on users vCards.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A> component.
<LI CLASS="li-itemize">Support for virtual hosting.
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0025.html">HTTP Polling</A> service.
<LI CLASS="li-itemize">IRC transport.
</UL>
</UL>
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>
+136
View File
@@ -0,0 +1,136 @@
\documentclass[a4paper,10pt]{article}
%% Packages
\usepackage{epsfig}
\usepackage{fancyhdr}
\usepackage{graphics}
\usepackage{hevea}
\usepackage[pdftex,colorlinks,unicode,urlcolor=blue,linkcolor=blue,pdftitle=Ejabberd\
Feature\ Sheet,pdfauthor=Sander\
Devrieze,pdfsubject=ejabberd,pdfkeywords=ejabberd]{hyperref}
\usepackage{verbatim}
\usepackage{color}
%% Index
% Remove the index anchors from the HTML version to save size and bandwith.
\newcommand{\ind}[1]{\begin{latexonly}\index{#1}\end{latexonly}}
%% Images
\newcommand{\logoscale}{0.7}
\newcommand{\imgscale}{0.58}
\newcommand{\insimg}[1]{\insscaleimg{\imgscale}{#1}}
\newcommand{\insscaleimg}[2]{
\imgsrc{#2}{}
\begin{latexonly}
\scalebox{#1}{\includegraphics{#2}}
\end{latexonly}
}
%% Various
\newcommand{\bracehack}{\def\{{\char"7B}\def\}{\char"7D}}
\newcommand{\titem}[1]{\item[\bracehack\texttt{#1}]}
\newcommand{\ns}[1]{\texttt{#1}}
\newcommand{\jid}[1]{\texttt{#1}}
\newcommand{\option}[1]{\texttt{#1}}
\newcommand{\poption}[1]{{\bracehack\texttt{#1}}}
\newcommand{\node}[1]{\texttt{#1}}
\newcommand{\term}[1]{\texttt{#1}}
\newcommand{\shell}[1]{\texttt{#1}}
\newcommand{\ejabberd}{\texttt{ejabberd}}
\newcommand{\Jabber}{Jabber}
%% Title page
\include{version}
\title{Ejabberd \version\ Feature Sheet}
\author{Sander Devrieze \\
\ahrefurl{mailto:s.devrieze@pandora.be} \\
\ahrefurl{xmpp:sander@devrieze.dyndns.org}}
% Options
\newcommand{\marking}[1]{\textbf{\begin{large}\textcolor{ejblue}{#1}\end{large}}} % Marking enabled
\newcommand{\quoting}[2][yozhik]{\begin{quotation}\textcolor{#1}{\textit{#2}}\end{quotation}} % Quotes enabled
\newcommand{\new}{\marginpar{\textcolor{red}{\textsc{new}}}} % Highlight new features
\newcommand{\improved}{\marginpar{\textcolor{orange}{\textsc{improved}}}} % Highlight improved features
\setcounter{secnumdepth}{-1} % Disable section numbering
%% To by-pass errors in the HTML version.
\newstyle{SPAN}{width:20\%; float:right; text-align:left; margin-left:auto;}
\definecolor{orange} {cmyk}{0.000,0.333,1.000,0.000}
%% Footnotes
\begin{latexonly}
\global\parskip=9pt plus 3pt minus 1pt
\global\parindent=0pt
\gdef\ahrefurl#1{\href{#1}{\texttt{#1}}}
\gdef\footahref#1#2{#2\footnote{\href{#1}{\texttt{#1}}}}
\end{latexonly}
\newcommand{\tjepref}[2]{\footahref{http://www.jabber.org/jeps/jep-#1.html}{#2}}
\newcommand{\jepref}[1]{\tjepref{#1}{JEP-#1}}
%% Fancy header
\fancyhf{}
\pagestyle{fancy}
\rhead{\textcolor{ejblue}{The Expandable Jabber Daemon.}}
\renewcommand{\headrule}{{\color{ejblue}%
\hrule width\headwidth height\headrulewidth \vskip-\headrulewidth}}
\lhead{\setlength{\unitlength}{-6mm}
\begin{picture}(0,0)
\put(5.8,3.25){\includegraphics[width=1.3\textwidth]{yozhikheader.png}}
\end{picture}}
% Official ejabberd colours
\definecolor{ejblue} {cmyk}{1.000,0.831,0.000,0.537} %RGB: 0,0,118 HTML: 000076
\definecolor{ejgreenyellow}{cmyk}{0.079,0.000,0.275,0.102} %RGB: 209,229,159 HTML: d1e59f
\definecolor{ejgreendark} {cmyk}{0.131,0.000,0.146,0.220} %RGB: 166,199,162 HTML: a6c7a2
\definecolor{ejgreen} {cmyk}{0.077,0.000,0.081,0.078} %RGB: 216,236,215 HTML: d8ecd7
\definecolor{ejgreenwhite} {cmyk}{0.044,0.000,0.048,0.020} %RGB: 239,250,238 HTML: effaee
\definecolor{yozhik} {cmyk}{0.000,0.837,1.000,0.424} %RGB: 147,0,0 HTML: 930000
\begin{document}
\label{titlepage}
\begin{titlepage}
\maketitle{}
\thispagestyle{empty}
\begin{center}
{\insscaleimg{\logoscale}{logo.png}
\par
}
\end{center}
\quoting{I can thoroughly recommend ejabberd for ease of setup --
Kevin Smith, Current maintainer of the Psi project}
\end{titlepage}
\newpage
% Set the page counter to 2 so that the titlepage and the second page do not
% have the same page number. This fixes the PDFLaTeX warning "destination with
% the same identifier".
\begin{latexonly}
\setcounter{page}{2}
\pagecolor{ejgreenwhite}
\end{latexonly}
% Input introduction.tex
\input{introduction}
\end{document}
%% TODO
% * illustrations (e.g. screenshot from web interface)
% * commented parts
% * slides, guide and html version
% * cleaning and improving LaTeX code
% * key features: something like this (shorter)? (more focussed on Erlang now): "To reach the goal of high
% availability, performance and clustering, ejabberd is written in Erlang, a programming language perfectly
% suited for this. Besides that, some parts are written in C to also incude the advantages of this language. In
% short, ejabberd is a perfect mix of mainly Erlang code, peppered with some C code to get the final touch!"
% <picture of a cocktail>
% * key features: saying that ejabberd the only XMPP server is that can do real clustering:
% http://www.jivesoftware.org/forums/thread.jspa?threadID=14602
% "What I find interesting is that *no* XMPP servers truly provide clustering. This includes all the commercial
% servers. The one partial exception appears to be ejabberd, which can cluster certain data such as sessions,
% but not all services such as MUC."
% * try it today: links to migration tutorials
+2820 -1098
View File
File diff suppressed because it is too large Load Diff
+1831 -727
View File
File diff suppressed because it is too large Load Diff
+54 -54
View File
@@ -1,25 +1,31 @@
\section{Introduction}
\label{sec:intr}
\section{\aname{intro}{Introduction}}
\label{sec:intro}
\quoting{I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. --
\quoting{I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. ---
Joeri}
\ejabberd{} is a free (GPL) distributed fault-tolerant \Jabber{}/XMPP server and is mainly written in \footahref{http://www.erlang.org/}{Erlang}.
%ejabberd is a free and open source instant messaging server written in Erlang. ejabberd is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication (Jabber/XMPP).
\ejabberd{} is designed to be a \marking{stable} and \marking{feature rich} \Jabber{}/XMPP server.
\ejabberd{} is a \marking{free and open source} instant messaging server written in \footahref{http://www.erlang.org/}{Erlang}.
\ejabberd{} is suitable for small servers, whether they need to be scalable or not, as well as extremely big servers.
\ejabberd{} is \marking{cross-platform}, distributed, fault-tolerant, and based on open standards to achieve real-time communication.
%\subsection{Layout with example deployment (title needs a better name)}
\ejabberd{} is designed to be a \marking{rock-solid and feature rich} XMPP server.
\ejabberd{} is suitable for small deployments, whether they need to be \marking{scalable} or not, as well as extremely big deployments.
%\subsection{\aname{layout}{Layout with example deployment (title needs a better name)}}
%\label{sec:layout}
%In this section there will be a graphical overview like these:\\
%\verb|http://www.tipic.com/var/timp/timp_dep.gif| \\
%\verb|http://www.jabber.com/images/jabber_Com_Platform.jpg| \\
%\verb|http://www.antepo.com/files/OPN45systemdatasheet.pdf| \\
%Some small images of Jabber clients that are known to work greatly with ejabberd. Less text!!!
%A page full with names of Jabber client that are known to work with ejabberd. \begin{tiny}tiny font\end{tiny}
%\subsection{Try It Today}
%\subsection{\aname{trytoday}{Try It Today}}
%\label{sec:trytoday}
%(Not sure if I will include/finish this section for the next version.)
@@ -32,100 +38,94 @@ Joeri}
%\end{itemize}
\newpage
\subsection{Key Features}
\subsection{\aname{keyfeatures}{Key Features}}
\label{sec:keyfeatures}
\ind{features!key features}
\quoting{Erlang seems to be tailor-made for writing stable, robust servers. --
\quoting{Erlang seems to be tailor-made for writing stable, robust servers. ---
Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
\ejabberd{} is:
\begin{itemize}
\item \marking{Multiplatform:} \ejabberd{} runs under Windows NT/2000/XP and Unix derived systems such as Linux, FreeBSD and NetBSD.
\item \marking{Cross-platform:} \ejabberd{} runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.
\item \marking{Distributed:} You can run \ejabberd{} on a cluster of machines and all of them will serve one \Jabber{} domain. When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support hundreds of concurrent users.
\item \marking{Distributed:} You can run \ejabberd{} on a cluster of machines and all of them will serve the same \Jabber{} domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.
\item \marking{Fault-tolerant:} You can deploy an \ejabberd{} cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ``on the fly''.
\item \marking{Administrator Friendly:} \ejabberd{} is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already installed, and ready to run out of the box. Other benefits for administrators include:
\item \marking{Fault-tolerant:} You can deploy an \ejabberd{} cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.
\item \marking{Administrator Friendly:} \ejabberd{} is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
\begin{itemize}
\item Comprehensive documentation.\moreinfo{ --- You can start in the \footahref{http://ejabberd.jabber.ru/book}{ejabberd Book}.}
\item Straightforward installers for Windows and Linux.\moreinfo{ --- (\footahref{http://ejabberd.jabber.ru/screenshots-linux-installer}{Screenshots}).}
\item Web interface for administration tasks.\moreinfo{ --- With HTTPS secure access. \footahref{http://ejabberd.jabber.ru/online-demo-webadmin}{Demo}.}
\item Shared Roster groups.\improved{}\moreinfo{ --- The administrator can setup a common list of \Jabber{} users for all users on the server. Those users are virtually added to all rosters. They cannot be removed, but can be renamed or moved into different roster groups. Does not require client implementation. Not related to \jepref{0144} (Roster Item Exchange).\footahref{http://ejabberd.jabber.ru/screenshots-shared-roster-groups}{Screenshots})}
\item Command line administration tool.\moreinfo{ --- Some basic administration tasks can be acomplished using the command line: register/remove users, backup/restore database, amongst others (\footahref{http://ejabberd.jabber.ru/screenshots-administration#ejabberdctl}{Screenshots}).}
\item Comprehensive documentation.
\item Straightforward installers for Linux, Mac OS X, and Windows.\improved{}
\item Web interface for administration tasks.
\item Shared Roster Groups.
\item Command line administration tool.\improved{}
\item Can integrate with existing authentication mechanisms.
\item Capability to send announce messages.
\item Statistics via \jepref{0039} (Statistics Gathering).
\end{itemize}
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
\begin{itemize}
\item Translated in 11 languages.\moreinfo{ --- More information is available \footahref{http://ejabberd.jabber.ru/localization}{here}.}
\item Translated in 12 languages.\improved{}
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
\item Support for \ns{xml:lang}.
\end{itemize}
\item \marking{Modular:} \ejabberd{}'s modular architecture allows easy customization:
\item \marking{Open Standards:} \ejabberd{} is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
\begin{itemize}
\item Load only the modules you want.
\item Extend \ejabberd{} with your own custom modules.\moreinfo{ --- A list of contributed modules and patches is available on the \footahref{http://ejabberd.jabber.ru/contributions}{contributions page}.}
\item Fully XMPP compliant.
\item XML-based protocol.
\item \footahref{http://ejabberd.jabber.ru/protocols}{Many JEPs supported}.
\end{itemize}
\end{itemize}
\newpage
\subsection{Additional Features}
\subsection{\aname{addfeatures}{Additional Features}}
\label{sec:addfeatures}
\ind{features!additional features}
\quoting{ejabberd is making inroads to solving the "buggy incomplete server" problem --
\quoting{ejabberd is making inroads to solving the "buggy incomplete server" problem ---
Justin Karneges, Founder of the Psi and the Delta projects}
Besides common \Jabber{} server features, \ejabberd{} comes with a wide range of other features:
Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features:
\begin{itemize}
\item The ability to interface via external components with networks such as:
\item Modular
\begin{itemize}
\item AIM
\item ICQ
\item MSN
\item Yahoo!
\end{itemize}
\item Open Standards
\begin{itemize}
\item \footahref{http://ejabberd.jabber.ru/protocols}{Many JEPs supported}.
\item XML-based protocol
\item Fully XMPP compliant for c2s connections (Core \& IM) \moreinfo{ --- ejabberd supports all XMPP Core 1.0 and XMPP IM 1.0, or is close to it. Check the \footahref{http://ejabberd.jabber.ru/protocols}{supported protocols}.}
\item Load only the modules you want.
\item Extend \ejabberd{} with your own custom modules.
\end{itemize}
\item Security
\begin{itemize}
\item SASL and STARTTLS for c2s connections.
\item STARTTLS and Dialback s2s connections.\new{}
\item Obsolete SSL for c2s connections also supported.
\item SASL and STARTTLS for c2s and s2s connections.
\item STARTTLS and Dialback s2s connections.
\item Web interface accessible via HTTPS secure access.
\end{itemize}
\item Databases
\begin{itemize}
\item Native MySQL support.
\item Native PostgreSQL support.
\item Mnesia.
\item ODBC data storage support (tested against PostgreSQL). \moreinfo{ --- ODBC requests can be load balanced between several connections.}
\item ODBC data storage support.
\item Microsoft SQL Server support.\new{}
\end{itemize}
\item Authentication
\begin{itemize}
\item LDAP. \moreinfo{ --- Accounts can authenticate in a LDAP server.}
\item LDAP and ODBC.\improved{}
\item External Authentication script.
\item Internal Authentication.
\end{itemize}
\item Others
\begin{itemize}
\item IPv6 support both for c2s and s2s connections.
\item Support for virtual hosting. \moreinfo{ --- Several \Jabber{} hosts can be hosted on the same \ejabberd{} instance. As simple as adding a new domain name to the list of hosts in the configuration file.}
\item \tjepref{0025}{HTTP Polling} service
\item \tjepref{0045}{Multi-User Chat} module.
\item IRC transport.
\item \tjepref{0060}{Publish-Subscribe} component.
\item Compressing XML streams with Stream Compression (\jepref{0138}).
\item Interface with networks such as AIM, ICQ and MSN.
\item Statistics via Statistics Gathering (\jepref{0039}).
\item IPv6 support both for c2s and s2s connections.
\item \tjepref{0045}{Multi-User Chat} module with logging.\improved{}
\item Users Directory based on users vCards.
\item \tjepref{0060}{Publish-Subscribe} component.
\item Support for virtual hosting.
\item \tjepref{0025}{HTTP Polling} service.
\item IRC transport.
\end{itemize}
\end{itemize}
View File
+66 -65
View File
@@ -1,112 +1,113 @@
Release notes
Release Notes
ejabberd 1.0.0
2005-12-13
14 December 2005
This document describes the main changes in ejabberd 1.0.0. This version is
compliant with the XMPP (eXtensible Messaging and Presence Protocol)
standard. ejabberd is the first Open Source server claiming to fully
comply to the XMPP standard.
This document describes the main changes in ejabberd 1.0.0. Unique in this
version is the compliancy with the XMPP (eXtensible Messaging and Presence
Protocol) standard. ejabberd is the first Open Source Jabber server claiming
to fully comply to the XMPP standard.
The code can be downloaded from the Process-one website:
ejabberd can be downloaded from the Process-one website:
http://www.process-one.net/en/projects/ejabberd/
For more detailed information, please refer to ejabberd User Guide
on the Process-one website:
Detailed information can be found in the ejabberd Feature Sheet and User
Guide which are available on the Process-one website:
http://www.process-one.net/en/projects/ejabberd/docs.html
Recent changes include....
Recent changes include:
Encrypted server-to-server connections
Server-to-server Encryption for Enhanced Security
- Support for STARTTLS+SASL EXTERNAL for Server-to-Server connections.
- STARTTLS+Dialback has been implemented for Server-to-Server (s2s)
connections. Those options are handled with two new config file options
(s2s_use_starttls and s2s_certfile). See ejabberd.cfg.example for usage
examples.
- DNSName certificate field and DNS name matching are supported.
- Per virtual host certificate can be defined.
- Support for STARTTLS and SASL EXTERNAL to secure server-to-server traffic
has been added.
- Also, STARTTLS and Dialback has been implemented for server-to-server (s2s)
connections. Detailed information about these new features can be found on
http://ejabberd.jabber.ru/s2s-encryption
- commonName and dNSName fields matching were introduced to ease the process
of retrieving certificates.
- Different certificates can be defined for each virtual host.
ODBC Support
- ODBC support has been improved to allow production use of ejabberd with
relational databases.
- Support for VCard storage in ODBC has been added.
- ejd2odbc.erl: is a program that convert an installation from Erlang Mnesia
database to an ODBC relational database.
- Support for vCard storage in ODBC has been added.
- ejd2odbc.erl is a tool to convert an installation from Erlang's database
Mnesia to an ODBC compatible relational database.
Native PostgreSQL support
Native PostgreSQL Support
- Native postgreSQL support has been added: You can now use a PostgreSQL
database without the need to go through the ODBC driver.
- Native PostgreSQL support gives you a better performance when you use
PostgreSQL.
Shared roster
Shared Roster groups
- Shared roster support have been improved: You can specify all users in a
virtual host for addition in a group.
- Shared Roster groups support has been enhanced. New is the ability to add
all registered users to everyone's roster. Detailed information about this
new feature can be found on http://ejabberd.jabber.ru/shared-roster-all
Web interface
Web Interface
- The Web interface internal code has been modified for better integration
and compliance with J-EAI.
- The Web interface is now compliant to XHTML 1.0 Transitional.
- Several bugs have been fixed.
- The web interface internal code has been modified for better integration
and compliancy with J-EAI, an ejabberd-based Enterprise Application
Integration platform.
- More XHTML 1.0 Transitional compliancy work was done.
Transports
- A transport workaround can be enabled with the compile time option. You
- A transport workaround can be enabled during compilation. To do this, you
can pass the "--enable-roster-gateway-workaround" option to the configure
script. For example:
./configure --enable-roster-gateway-workaround
This option allows to add items with subscription "to" in roster by
sending <presence type='subscribed'/> stanza to user. This option is
needed for JIT ICQ transport.
Warning: By using this option, ejabberd is not fully XMPP compliant.
script. (./configure --enable-roster-gateway-workaround)
This option allows transports to add items with subscription "to" in the
roster by sending <presence type='subscribed'/> stanza to user. This option
is only needed for JIT ICQ transport.
Warning: by enabling this option, ejabberd will not be fully XMPP compliant
anymore.
Documentation and translations
Documentation and Internationalization
- Documentation has been improved to cover more topics.
- Translations have been updated to support the new features.
- Documentation has been extended to cover more topics.
- Translations have been updated.
Bugfixes
- This release contains several bugfixes.
Among other bugfixes include improvements to the Client-to-Server
connection management module. Please refer to the ChangeLog file supplied
- Among other bugfixes include improvements to the client-to-server (c2s)
connection management module.
- Please refer to the ChangeLog file supplied
with this release regarding all improvements in ejabberd.
Erlang version supported
- You now need at least Erlang/OTP R9C to be able to run ejabberd 1.0.0.
Installation Notes
Installers
Installers are provided for Microsoft Windows and Linux/x86. The Linux
installer includes Erlang ASN.1 modules for LDAP authentication support.
Installers are available from:
Supported Erlang Version
- You need at least Erlang/OTP R9C to run ejabberd 1.0.0.
Installation
Installers are provided for Microsoft Windows and Linux/x86.
Installers can be retrieved from:
http://www.process-one.net/en/projects/ejabberd/download.html
Migration
Migration Notes
- Before any migration, ejabberd system and database must be properly
backed-up.
- System migrating from a previous ODBC based install will need to change
their relational database schema. The following SQL commands must be run
on the database:
CREATE SEQUENCE spool_seq_seq;
ALTER TABLE spool ADD COLUMN seq integer;
ALTER TABLE spool ALTER COLUMN seq SET DEFAULT nextval('spool_seq_seq');
UPDATE spool SET seq = DEFAULT;
ALTER TABLE spool ALTER COLUMN seq SET NOT NULL;
backed up.
- When upgrading an ODBC-based installation, you will need to change the
relational database schema. The following SQL commands must be run on the
database:
CREATE SEQUENCE spool_seq_seq;
ALTER TABLE spool ADD COLUMN seq integer;
ALTER TABLE spool ALTER COLUMN seq SET DEFAULT nextval('spool_seq_seq');
UPDATE spool SET seq = DEFAULT;
ALTER TABLE spool ALTER COLUMN seq SET NOT NULL;
References
The ejabberd feature sheet helps comparing with other Jabber/XMPP
servers:
http://www.process-one.net/en/projects/ejabberd/docs/features.pdf
Contributed tutorials of interest are:
- Migration from Jabberd1.4 to ejabberd:
http://ejabberd.jabber.ru/jabberd1-to-ejabberd
+115
View File
@@ -0,0 +1,115 @@
Release Notes
ejabberd 1.1.0
24 April 2006
This document describes the main changes in ejabberd 1.1.0. This version
introduce new features including support for new Jabber Enhancement
Proposals and several performance improvements enabling deployments on an
even larger scale than already possible.
ejabberd can be downloaded from the Process-one website:
http://www.process-one.net/en/projects/ejabberd/
Detailed information can be found in the ejabberd Feature Sheet and User
Guide which are available on the Process-one website:
http://www.process-one.net/en/projects/ejabberd/docs.html
A complete list of changes is available from:
http://support.process-one.net/secure/ReleaseNote.jspa?projectId=10011&styleName=Html&version=10025
Recent changes include:
New Jabber Enhancement Proposal support:
- JEP-0050: Ad-Hoc Commands.
- JEP-0138: Stream Compression.
- JEP-0175: SASL anonymous.
Anonymous login
- SASL anonymous.
- Anonymous login for clients that do not yet support SASL Anonymous.
Relational database Support
- MySQL is now fully supported through ODBC and in native mode.
- Various improvements to the native database interfaces.
- The migration tool can use relational databases.
Multi-User Chat improvements
- Logging of room discussion to text file is now supported.
- Better reconfiguration support.
- Security oriented fixes.
- Several improvements and updates to latest JEP-0045.
Performance scalability improvements for large clusters
- Improved session synchronisation management between cluster nodes.
- Internal architecture has been reworked to use generalize Erlang/OTP
framework usage.
- Speed improvement on logger.
- TCP/IP packet reception change for better network throttling and
regulation.
As a result, these improvements will reduce load on large scale deployments.
XMPP Protocol related improvements
- XML stanza size can be limited.
- Messages are send to all resources with the same highest priority.
Documentation and Internationalization
- Documentation has been extended to cover more topics.
- Translations have been updated.
Web interface
- XHTML 1.0 compliance.
Bugfixes
- This release contains many bugfixes on various areas such as Publish-Subscribe, build
chain, installers, IRC gateway, ejabberdctl, amongst others.
- Please refer to the ChangeLog file supplied with this release regarding
all improvements in ejabberd.
Installation Notes
Supported Erlang Version
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.0.
Installation
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
Installers can be retrieved from:
http://www.process-one.net/en/projects/ejabberd/download.html
Migration Notes
- Before any migration, ejabberd system and database must be properly
backed up.
- The database schema has not been changed comparing to version 1.0.0 and
consequently it does not require any migration.
References
Contributed tutorials and documents of interest are:
- Migration from Jabberd1.4, Jabberd2 and WPJabber to ejabberd:
http://ejabberd.jabber.ru/migrate-to-ejabberd
- Transport configuration for connecting to other networks:
http://ejabberd.jabber.ru/tutorials-transports
- Using ejabberd with MySQL native driver:
http://support.process-one.net/doc/display/MESSENGER/Using+ejabberd+with+MySQL+native+driver
- Anonymous User Support:
http://support.process-one.net/doc/display/MESSENGER/Anonymous+users+support
- Frequently Asked Questions:
http://ejabberd.jabber.ru/faq
END
+119
View File
@@ -0,0 +1,119 @@
Release Notes
ejabberd 1.1.1
28 April 2006
This document describes the main changes in ejabberd 1.1.x. This version
introduce new features including support for new Jabber Enhancement
Proposals and several performance improvements enabling deployments on an
even larger scale than already possible.
This release fix a security issue introduced in ejabberd 1.1.0. In SASL
mode, anonymous login was enabled as a default. Upgrading ejabberd 1.1.0 to
ejabberd 1.1.1 is highly recommended.
ejabberd can be downloaded from the Process-one website:
http://www.process-one.net/en/projects/ejabberd/
Detailed information can be found in the ejabberd Feature Sheet and User
Guide which are available on the Process-one website:
http://www.process-one.net/en/projects/ejabberd/docs.html
A complete list of changes is available from:
http://support.process-one.net/secure/ReleaseNote.jspa?projectId=10011&styleName=Html&version=10025
Recent changes include:
New Jabber Enhancement Proposal support:
- JEP-0050: Ad-Hoc Commands.
- JEP-0138: Stream Compression.
- JEP-0175: SASL anonymous.
Anonymous login
- SASL anonymous.
- Anonymous login for clients that do not yet support SASL Anonymous.
Relational database Support
- MySQL is now fully supported through ODBC and in native mode.
- Various improvements to the native database interfaces.
- The migration tool can use relational databases.
Multi-User Chat improvements
- Logging of room discussion to text file is now supported.
- Better reconfiguration support.
- Security oriented fixes.
- Several improvements and updates to latest JEP-0045.
Performance scalability improvements for large clusters
- Improved session synchronisation management between cluster nodes.
- Internal architecture has been reworked to use generalize Erlang/OTP
framework usage.
- Speed improvement on logger.
- TCP/IP packet reception change for better network throttling and
regulation.
As a result, these improvements will reduce load on large scale deployments.
XMPP Protocol related improvements
- XML stanza size can be limited.
- Messages are send to all resources with the same highest priority.
Documentation and Internationalization
- Documentation has been extended to cover more topics.
- Translations have been updated.
Web interface
- XHTML 1.0 compliance.
Bugfixes
- This release contains many bugfixes on various areas such as Publish-Subscribe, build
chain, installers, IRC gateway, ejabberdctl, amongst others.
- Please refer to the ChangeLog file supplied with this release regarding
all improvements in ejabberd.
Installation Notes
Supported Erlang Version
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.0.
Installation
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
Installers can be retrieved from:
http://www.process-one.net/en/projects/ejabberd/download.html
Migration Notes
- Before any migration, ejabberd system and database must be properly
backed up.
- The database schema has not been changed comparing to version 1.0.0 and
consequently it does not require any migration.
References
Contributed tutorials and documents of interest are:
- Migration from Jabberd1.4, Jabberd2 and WPJabber to ejabberd:
http://ejabberd.jabber.ru/migrate-to-ejabberd
- Transport configuration for connecting to other networks:
http://ejabberd.jabber.ru/tutorials-transports
- Using ejabberd with MySQL native driver:
http://support.process-one.net/doc/display/MESSENGER/Using+ejabberd+with+MySQL+native+driver
- Anonymous User Support:
http://support.process-one.net/doc/display/MESSENGER/Anonymous+users+support
- Frequently Asked Questions:
http://ejabberd.jabber.ru/faq
END
+119
View File
@@ -0,0 +1,119 @@
Release Notes
ejabberd 1.1.2
27 September 2006
This document describes the main changes in ejabberd 1.1.2.
This version is a major improvement over ejabberd 1.1.1, improving the
overall behaviour of the server in many areas. Users of ejabberd 1.1.1
should upgrade to this new release for improved robustness and compliance.
ejabberd can be downloaded from the Process-one website:
http://www.process-one.net/en/projects/ejabberd/
Detailed information can be found in the Feature Sheet and in the
Installation and Operation Guide which are both available on the
Process-one website:
http://www.process-one.net/en/projects/ejabberd/docs.html
ejabberd includes 44 improvements. A complete list of changes can be
retrieved from:
http://redir.process-one.net/ejabberd-1.1.2
Recent changes include:
LDAP Improvements
- Major improvements have been made on the LDAP module. It is now more
flexible and more robust.
HTTP Polling Fixes
- The HTTP polling modules have been fixed and improved: the connections are
closed properly and polled messages cannot be lost anymore.
Roster Management Improvement
- Roster management improvements increase reliability, especially in cases
where users are on different servers.
- Shared rosters are more reliable.
Improved Robustness
- It is now possible to limit the number of opened connections for a single
user.
Relational databases
- Database support: Microsoft SQL Server is now officially supported in ODBC
mode.
Publish-Subscribe Improvement
- Restricting node creation with a dedicated ACL rule is now possible.
Localization
- A Czech translation has been added.
- Translations have been updated.
Binary Installer
- New binary installer for Windows including all requirements.
- Improved installers for Linux and MacOSX (PowerPC)
XMPP Compliancy
- Some protocol compliance fix have been added, after the Portland XMPP
Interop Meeting in July.
Miscelanous
- MUC have been improved (logging rendering).
- The command line tool ejabberdctl has been improved.
- The build chain has been improved, including MacOSX support.
- The documentation has been improved and updated to describe the new
features.
Bugfixes
- Anonymous login bugfixes.
- Please refer to the ChangeLog file supplied with this release regarding
all improvements in ejabberd.
Installation Notes
Supported Erlang Version
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.2.
- The recommanded version is Erlang/OTP R10B-10.
- Erlang/OTP R11B has not yet been fully certified for ejabberd.
Installation
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
They can be retrieved from:
http://www.process-one.net/en/projects/ejabberd/download.html
Migration Notes
- Before any migration, ejabberd system and database must be properly
backed up.
- The relational database schema has changed between version 1.1.1 and
1.1.2. An "askmessage" column needs to be added in the "rosterusers" table
to perform the migration.
References
Contributed tutorials and documents of interest are:
- Migration from other XMPP servers to ejabberd:
http://ejabberd.jabber.ru/migrate-to-ejabberd
- Transport configuration for connecting to other networks:
http://ejabberd.jabber.ru/tutorials-transports
- Frequently Asked Questions:
http://ejabberd.jabber.ru/faq
END
+1 -1
View File
@@ -1,2 +1,2 @@
% Define ejabberd version here.
\newcommand{\version}{1.0.0}
\newcommand{\version}{1.1.2}
+18 -6
View File
@@ -17,29 +17,40 @@ ifdef debug
ERLC_FLAGS+=+debug_info
endif
ifdef ejabberd_debug
ERLC_FLAGS+=-Dejabberd_debug
endif
ifeq (@roster_gateway_workaround@, true)
ERLC_FLAGS+=-DROSTER_GATEWAY_WORKAROUND
endif
prefix = @prefix@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS = expat_erl.so
SOURCES = $(wildcard *.erl)
BEAMS = $(SOURCES:.erl=.beam)
DESTDIR =
DESTDIR =
EJABBERDDIR = $(DESTDIR)/var/lib/ejabberd
EJABBERDDIR = $(DESTDIR)@prefix@/var/lib/ejabberd
BEAMDIR = $(EJABBERDDIR)/ebin
PRIVDIR = $(EJABBERDDIR)/priv
SODIR = $(PRIVDIR)/lib
MSGSDIR = $(PRIVDIR)/msgs
LOGDIR = $(DESTDIR)/var/log/ejabberd
ETCDIR = $(DESTDIR)/etc/ejabberd
LOGDIR = $(DESTDIR)@prefix@/var/log/ejabberd
ETCDIR = $(DESTDIR)@prefix@/etc/ejabberd
ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
else
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
endif
all: $(ERLSHLIBS) compile-beam all-recursive
compile-beam: XmppAddr.hrl $(BEAMS)
@@ -66,7 +77,7 @@ $(ERLSHLIBS): %.so: %.c
$(subst ../,,$(subst .so,.c,$@)) \
$(EXPAT_LIBS) $(EXPAT_CFLAGS) \
$(ERLANG_LIBS) $(ERLANG_CFLAGS) \
-o $@ -fpic -shared
-o $@ $(DYNAMIC_LIB_CFLAGS)
install: all
install -d $(BEAMDIR)
@@ -85,6 +96,7 @@ clean: clean-recursive clean-local
clean-local:
rm -f *.beam $(ERLSHLIBS)
rm -f XmppAddr.asn1db XmppAddr.erl XmppAddr.hrl
distclean: distclean-recursive clean-local
rm -f config.status
+17 -2
View File
@@ -65,10 +65,15 @@ release : build release_clean
copy stringprep\*.tcl $(SRC_DIR)\stringprep
mkdir $(SRC_DIR)\tls
copy tls\*.erl $(SRC_DIR)\tls
copy tls\*.c $(SRC_DIR)\tls
mkdir $(SRC_DIR)\ejabberd_zlib
copy ejabberd_zlib\*.erl $(SRC_DIR)\ejabberd_zlib
copy ejabberd_zlib\*.c $(SRC_DIR)\ejabberd_zlib
mkdir $(SRC_DIR)\web
copy web\*.erl $(SRC_DIR)\web
mkdir $(SRC_DIR)\odbc
copy odbc\*.erl $(SRC_DIR)\odbc
copy odbc\*.sql $(EREL)
mkdir $(DOC_DIR)
copy ..\doc\*.txt $(DOC_DIR)
copy ..\doc\*.html $(DOC_DIR)
@@ -93,14 +98,19 @@ all-recursive :
nmake -nologo -f Makefile.win32
cd ..\tls
nmake -nologo -f Makefile.win32
cd ..\ejabberd_zlib
nmake -nologo -f Makefile.win32
cd ..\web
nmake -nologo -f Makefile.win32
cd ..\odbc
nmake -nologo -f Makefile.win32
cd ..
compile-beam :
erlc *.erl
compile-beam : XmppAddr.hrl
erl -s make all report -noinput -s erlang halt
XmppAddr.hrl : XmppAddr.asn1
erlc -bber_bin +der +compact_bit_string +optimize +noobj XmppAddr.asn1
CLEAN : clean-recursive clean-local
@@ -110,6 +120,9 @@ clean-local :
-@erase expat_erl.exp
-@erase expat_erl.lib
-@erase *.beam
-@erase XmppAddr.asn1db
-@erase XmppAddr.erl
-@erase XmppAddr.hrl
clean-recursive :
cd eldap
@@ -124,6 +137,8 @@ clean-recursive :
nmake -nologo -f Makefile.win32 clean
cd ..\tls
nmake -nologo -f Makefile.win32 clean
cd ..\ejabberd_zlib
nmake -nologo -f Makefile.win32 clean
cd ..\web
nmake -nologo -f Makefile.win32 clean
cd ..\odbc
+7 -1
View File
@@ -171,7 +171,13 @@ match_acl(ACL, JID, Host) ->
is_glob_match(Server, SR);
{node_glob, UR, SR} ->
is_glob_match(Server, SR) andalso
is_glob_match(User, UR)
is_glob_match(User, UR);
WrongSpec ->
?ERROR_MSG(
"Wrong ACL expression: ~p~n"
"Check your config file and reload it with the override_acls option enabled",
[WrongSpec]),
false
end
end,
ets:lookup(acl, {ACL, global}) ++
+32 -1
View File
@@ -29,6 +29,37 @@ AC_DEFUN(AM_WITH_EXPAT,
AC_SUBST(EXPAT_LIBS)
])
AC_DEFUN(AM_WITH_ZLIB,
[ AC_ARG_WITH(zlib,
[ --with-zlib=PREFIX prefix where zlib is installed])
ZLIB_CFLAGS=
ZLIB_LIBS=
if test x"$with_zlib" != x; then
ZLIB_CFLAGS="-I$with_zlib/include"
ZLIB_LIBS="-L$with_zlib/lib"
fi
AC_CHECK_LIB(z, gzgets,
[ ZLIB_LIBS="$ZLIB_LIBS -lz"
zlib_found=yes ],
[ zlib_found=no ],
"$ZLIB_LIBS")
if test $zlib_found = no; then
AC_MSG_ERROR([Could not find the zlib library])
fi
zlib_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
if test $zlib_found = no; then
AC_MSG_ERROR([Could not find zlib.h])
fi
CFLAGS="$zlib_save_CFLAGS"
AC_SUBST(ZLIB_CFLAGS)
AC_SUBST(ZLIB_LIBS)
])
AC_DEFUN(AM_WITH_ERLANG,
[ AC_ARG_WITH(erlang,
[ --with-erlang=PREFIX path to erlc and erl ])
@@ -37,7 +68,7 @@ AC_DEFUN(AM_WITH_ERLANG,
AC_PATH_TOOL(ERLC, erlc, , $PATH:$with_erlang:$with_erlang/bin)
AC_PATH_TOOL(ERL, erl, , $PATH:$with_erlang:$with_erlang/bin)
if test "z$ERLC" == "z" || test "z$ERL" == "z"; then
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
AC_MSG_ERROR([erlang not found])
fi
+111
View File
@@ -0,0 +1,111 @@
%%%----------------------------------------------------------------------
%%% File : adhoc.erl
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050)
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(adhoc).
-author('henoch@dtek.chalmers.se').
-vsn('$Revision$ ').
-export([parse_request/1,
produce_response/2,
produce_response/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
%% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple.
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
Node = xml:get_tag_attr_s("node", SubEl),
SessionID = xml:get_tag_attr_s("sessionid", SubEl),
Action = xml:get_tag_attr_s("action", SubEl),
XData = find_xdata_el(SubEl),
{xmlelement, _, _, AllEls} = SubEl,
if XData ->
Others = lists:delete(XData, AllEls);
true ->
Others = AllEls
end,
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID,
action = Action,
xdata = XData,
others = Others};
parse_request(_) ->
{error, ?ERR_BAD_REQUEST}.
%% Borrowed from mod_vcard.erl
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
find_xdata_el1(SubEls).
find_xdata_el1([]) ->
false;
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_XDATA ->
{xmlelement, Name, Attrs, SubEls};
_ ->
find_xdata_el1(Els)
end;
find_xdata_el1([_ | Els]) ->
find_xdata_el1(Els).
%% Produce a <command/> node to use as response from an adhoc_response
%% record, filling in values for language, node and session id from
%% the request.
produce_response(#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID},
Response) ->
produce_response(Response#adhoc_response{lang = Lang,
node = Node,
sessionid = SessionID}).
%% Produce a <command/> node to use as response from an adhoc_response
%% record.
produce_response(#adhoc_response{lang = _Lang,
node = Node,
sessionid = ProvidedSessionID,
status = Status,
defaultaction = DefaultAction,
actions = Actions,
notes = Notes,
elements = Elements}) ->
SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" ->
ProvidedSessionID;
true ->
jlib:now_to_utc_string(now())
end,
case Actions of
[] ->
ActionsEls = [];
_ ->
case DefaultAction of
"" ->
ActionsElAttrs = [];
_ ->
ActionsElAttrs = [{"execute", DefaultAction}]
end,
ActionsEls = [{xmlelement, "actions",
ActionsElAttrs,
[{xmlelement, Action, [], []} || Action <- Actions]}]
end,
NotesEls = lists:map(fun({Type, Text}) ->
{xmlelement, "note",
[{"type", Type}],
[{xmlcdata, Text}]}
end, Notes),
{xmlelement, "command",
[{"xmlns", ?NS_COMMANDS},
{"sessionid", SessionID},
{"node", Node},
{"status", atom_to_list(Status)}],
ActionsEls ++ NotesEls ++ Elements}.
+15
View File
@@ -0,0 +1,15 @@
-record(adhoc_request, {lang,
node,
sessionid,
action,
xdata,
others}).
-record(adhoc_response, {lang,
node,
sessionid,
status,
defaultaction = "",
actions = [],
notes = [],
elements = []}).
+2714 -1599
View File
File diff suppressed because it is too large Load Diff
+34 -2
View File
@@ -14,10 +14,15 @@ AM_WITH_ERLANG
AM_ICONV
#locating libexpat
AM_WITH_EXPAT
#locating zlib
AM_WITH_ZLIB
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
# Change default prefix
AC_PREFIX_DEFAULT /
# Checks for library functions.
AC_FUNC_MALLOC
AC_HEADER_STDC
@@ -29,9 +34,10 @@ AC_MOD_ENABLE(eldap, yes)
AC_MOD_ENABLE(web, yes)
AC_MOD_ENABLE(tls, yes)
AC_MOD_ENABLE(odbc, no)
AC_MOD_ENABLE(ejabberd_zlib, yes)
AC_ARG_ENABLE(roster_gateway_workaround,
[ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions],
[ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions (default: no)],
[case "${enableval}" in
yes) roster_gateway_workaround=true ;;
no) roster_gateway_workaround=false ;;
@@ -39,6 +45,15 @@ AC_ARG_ENABLE(roster_gateway_workaround,
esac],[roster_gateway_workaround=false])
AC_SUBST(roster_gateway_workaround)
AC_ARG_ENABLE(mssql,
[ --enable-mssql Use Microsoft SQL Server database (default: no, requires --enable-odbc)],
[case "${enableval}" in
yes) db_type=mssql ;;
no) db_type=generic ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-mssql) ;;
esac],[db_type=generic])
AC_SUBST(db_type)
AC_CONFIG_FILES([Makefile
$make_mod_irc
$make_mod_muc
@@ -47,7 +62,24 @@ AC_CONFIG_FILES([Makefile
$make_web
stringprep/Makefile
$make_tls
$make_odbc])
$make_odbc
$make_ejabberd_zlib])
#openssl
AM_WITH_OPENSSL
# If ssl is kerberized it need krb5.h
# On RedHat and OpenBSD, krb5.h is in an unsual place:
KRB5_INCLUDE="`krb5-config --cflags 2>/dev/null`"
if test -n "$KRB5_INCLUDE" ; then
CPPFLAGS="$CPPFLAGS $KRB5_INCLUDE"
else
# For RedHat For BSD
for D in /usr/kerberos/include /usr/include/kerberos /usr/include/kerberosV
do
if test -d $D ; then
CPPFLAGS="$CPPFLAGS -I$D"
fi
done
fi
AC_CHECK_HEADER(krb5.h,,)
AC_OUTPUT
+14 -4
View File
@@ -28,35 +28,45 @@ start() ->
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\StaticLibs\\libexpatMT.lib\n",
ExpatFlag = "EXPAT_FLAG = -DXML_STATIC\n",
IconvDir = "ICONV_DIR = c:\\progra~1\\libiconv-1.9.1-static\n",
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n";
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n",
ZlibDir = "ZLIB_DIR = c:\\progra~1\\zlib-1.2.3\n",
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n";
false ->
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\Libs\\libexpat.lib\n",
ExpatFlag = "",
IconvDir = "ICONV_DIR = c:\\progra~1\\libiconv-1.9.1\n",
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n"
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n",
ZlibDir = "ZLIB_DIR = c:\\progra~1\\zlib-1.2.3\n",
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib1.lib\n"
end,
EVersion = "ERLANG_VERSION = " ++ erlang:system_info(version) ++ "\n",
EIDirS = "EI_DIR = " ++ code:lib_dir("erl_interface") ++ "\n",
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
ExpatDir = "EXPAT_DIR = c:\\progra~1\\expat-1.95.7\n",
OpenSSLDir = "OPENSSL_DIR = c:\\progra~1\\OpenSSL\n",
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
SSLDir = "SSLDIR = " ++ code:lib_dir("ssl") ++ "\n",
StdLibDir = "STDLIBDIR = " ++ code:lib_dir("stdlib") ++ "\n",
file:write_file("Makefile.inc",
list_to_binary(EIDirS ++
list_to_binary(EVersion ++
EIDirS ++
RootDirS ++
Version ++
SSLDir ++
StdLibDir ++
OpenSSLDir ++
DBType ++
ExpatDir ++
ExpatLib ++
ExpatFlag ++
IconvDir ++
IconvLib)),
IconvLib ++
ZlibDir ++
ZlibLib)),
halt().
+37 -22
View File
@@ -25,8 +25,7 @@
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
[{mech_new, 2},
{mech_step, 2}];
[{mech_new, 3}, {mech_step, 2}];
behaviour_info(Other) ->
undefined.
@@ -36,6 +35,7 @@ start() ->
{keypos, #sasl_mechanism.mechanism}]),
cyrsasl_plain:start([]),
cyrsasl_digest:start([]),
cyrsasl_anonymous:start([]),
ok.
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
@@ -81,18 +81,19 @@ check_credentials(State, Props) ->
listmech(Host) ->
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
ets:select(sasl_mechanism,
[{#sasl_mechanism{mechanism = '$1',
require_plain_password = '$2',
_ = '_'},
if
RequirePlainPassword ->
[{'==', '$2', false}];
true ->
[]
end,
['$1']}]).
Mechs = ets:select(sasl_mechanism,
[{#sasl_mechanism{mechanism = '$1',
require_plain_password = '$2',
_ = '_'},
if
RequirePlainPassword ->
[{'==', '$2', false}];
true ->
[]
end,
['$1']}]),
filter_anonymous(Host, Mechs).
server_new(Service, ServerFQDN, UserRealm, SecFlags,
GetPassword, CheckPassword) ->
@@ -103,14 +104,21 @@ server_new(Service, ServerFQDN, UserRealm, SecFlags,
check_password = CheckPassword}.
server_start(State, Mech, ClientIn) ->
case ets:lookup(sasl_mechanism, Mech) of
[#sasl_mechanism{module = Module}] ->
{ok, MechState} = Module:mech_new(State#sasl_state.get_password,
State#sasl_state.check_password),
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
_ ->
case lists:member(Mech, listmech(State#sasl_state.myname)) of
true ->
case ets:lookup(sasl_mechanism, Mech) of
[#sasl_mechanism{module = Module}] ->
{ok, MechState} = Module:mech_new(
State#sasl_state.myname,
State#sasl_state.get_password,
State#sasl_state.check_password),
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
_ ->
{error, "no-mechanism"}
end;
false ->
{error, "no-mechanism"}
end.
@@ -132,3 +140,10 @@ server_step(State, ClientIn) ->
{error, Error}
end.
%% Remove the anonymous mechanism from the list if not enabled for the given
%% host
filter_anonymous(Host, Mechs) ->
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
true -> Mechs;
false -> Mechs -- ["ANONYMOUS"]
end.
+39
View File
@@ -0,0 +1,39 @@
%%%----------------------------------------------------------------------
%%% File : cyrsasl_anonymous.erl
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
%%% Purpose : ANONYMOUS SASL mechanism
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%% Id : $Id$
%%%----------------------------------------------------------------------
%% See http://www.ietf.org/internet-drafts/draft-ietf-sasl-anon-05.txt
-module(cyrsasl_anonymous).
-vsn('$Revision$').
-export([start/1, stop/0, mech_new/3, mech_step/2]).
-behaviour(cyrsasl).
-record(state, {server}).
start(_Opts) ->
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
ok.
stop() ->
ok.
mech_new(Host, _GetPassword, _CheckPassword) ->
{ok, #state{server = Host}}.
mech_step(State, _ClientIn) ->
%% We generate a random username:
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
Server = State#state.server,
%% Checks that the username is available
case ejabberd_auth:is_user_exists(User, Server) of
true -> {error, "not-authorized"};
false -> {ok, [{username, User}]}
end.
+2 -2
View File
@@ -12,7 +12,7 @@
-export([start/1,
stop/0,
mech_new/2,
mech_new/3,
mech_step/2]).
-behaviour(cyrsasl).
@@ -25,7 +25,7 @@ start(_Opts) ->
stop() ->
ok.
mech_new(GetPassword, _CheckPassword) ->
mech_new(_Host, GetPassword, _CheckPassword) ->
{ok, #state{step = 1,
nonce = randoms:get_string(),
get_password = GetPassword}}.
+2 -2
View File
@@ -10,7 +10,7 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start/1, stop/0, mech_new/2, mech_step/2, parse/1]).
-export([start/1, stop/0, mech_new/3, mech_step/2, parse/1]).
-behaviour(cyrsasl).
@@ -23,7 +23,7 @@ start(_Opts) ->
stop() ->
ok.
mech_new(_GetPassword, CheckPassword) ->
mech_new(_Host, _GetPassword, CheckPassword) ->
{ok, #state{check_password = CheckPassword}}.
mech_step(State, ClientIn) ->
+1 -1
View File
@@ -2,7 +2,7 @@
{application, ejabberd,
[{description, "ejabberd"},
{vsn, "1.0.0"},
{vsn, "1.1.2"},
{modules, [acl,
configure,
cyrsasl,
+30 -9
View File
@@ -20,6 +20,8 @@
%{acl, test, {user_regexp, "^test"}}.
%{acl, test, {user_glob, "test*"}}.
% Everybody can create pubsub nodes
{access, pubsub_createnode, [{allow, all}]}.
% Only admins can use configuration interface:
{access, configure, [{allow, admin}]}.
@@ -73,11 +75,11 @@
{access, local, [{allow, local}]}.
% Authentification method. If you want to use internal user base, then use
% Authentication method. If you want to use internal user base, then use
% this line:
{auth_method, internal}.
% For LDAP authentification use these lines instead of above one:
% For LDAP authentication use these lines instead of above one:
%{auth_method, ldap}.
%{ldap_servers, ["localhost"]}. % List of LDAP servers
%{ldap_uidattr, "uid"}. % LDAP attribute that holds user ID
@@ -85,11 +87,11 @@
%{ldap_rootdn, "dc=example,dc=com"}. % LDAP manager
%{ldap_password, "******"}. % Password to LDAP manager
% For authentification via external script use the following:
% For authentication via external script use the following:
%{auth_method, external}.
%{extauth_program, "/path/to/authentification/script"}.
%{extauth_program, "/path/to/authentication/script"}.
% For authentification via ODBC use the following:
% For authentication via ODBC use the following:
%{auth_method, odbc}.
%{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}.
@@ -97,6 +99,18 @@
% Host name:
{hosts, ["localhost"]}.
%% Define the maximum number of time a single user is allowed to connect:
{max_user_sessions, 10}.
%% Anonymous login support:
%% auth_method: anonymous
%% anonymous_protocol: sasl_anon|login_anon|both
%% allow_multiple_connections: true|false
%%{host_config, "public.example.org", [{auth_method, anonymous},
%% {allow_multiple_connections, false},
%% {anonymous_protocol, sasl_anon}]}.
%% To use both anonymous and internal authentication:
%%{host_config, "public.example.org", [{auth_method, [anonymous, internal]}]}.
% Default language for server messages
{language, "en"}.
@@ -104,13 +118,17 @@
% Listened ports:
{listen,
[{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper},
{max_stanza_size, 65536},
starttls, {certfile, "./ssl.pem"}]},
{5223, ejabberd_c2s, [{access, c2s},
{max_stanza_size, 65536},
tls, {certfile, "./ssl.pem"}]},
% Use these two lines instead if TLS support is not compiled
%{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper}]},
%{5223, ejabberd_c2s, [{access, c2s}, ssl, {certfile, "./ssl.pem"}]},
{5269, ejabberd_s2s_in, [{shaper, s2s_shaper}]},
{5269, ejabberd_s2s_in, [{shaper, s2s_shaper},
{max_stanza_size, 131072}
]},
{5280, ejabberd_http, [http_poll, web_admin]},
{8888, ejabberd_service, [{access, all},
{hosts, ["icq.localhost", "sms.localhost"],
@@ -134,13 +152,14 @@
{mod_register, [{access, register}]},
{mod_roster, []},
{mod_privacy, []},
{mod_configure, []},
{mod_adhoc, []},
{mod_configure, []}, % Depends on mod_adhoc
{mod_configure2, []},
{mod_disco, []},
{mod_stats, []},
{mod_vcard, []},
{mod_offline, []},
{mod_announce, [{access, announce}]},
{mod_announce, [{access, announce}]}, % Depends on mod_adhoc
{mod_echo, [{host, "echo.localhost"}]},
{mod_private, []},
{mod_irc, []},
@@ -152,7 +171,9 @@
{mod_muc, [{access, muc},
{access_create, muc},
{access_admin, muc_admin}]},
{mod_pubsub, []},
% {mod_muc_log, []},
% {mod_shared_roster, []},
{mod_pubsub, [{access_createnode, pubsub_createnode}]},
{mod_time, []},
{mod_last, []},
{mod_version, []}
+1 -1
View File
@@ -6,7 +6,7 @@
%%% Id : $Id$
%%%----------------------------------------------------------------------
-define(VERSION, "1.0.0").
-define(VERSION, "1.1.2").
%-define(ejabberd_debug, true).
%-define(DBGFSM, true).
+59
View File
@@ -0,0 +1,59 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_admin.erl
%%% Author : Mickael Remond <mremond@process-one.net>
%%% Description : This module gathers admin functions used by different
%%% access method:
%%% - ejabberdctl command-line tool
%%% - web admin interface
%%% - adhoc mode
%%%
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
%%% Id : $Id: $
%%%----------------------------------------------------------------------
%%% Copyright (c) 2006 Process One
%%%-------------------------------------------------------------------
-module(ejabberd_admin).
-author('mickael.remond@process-one.net').
-vsn('$Revision: $ ').
-export([restore/1]).
-include("ejabberd.hrl").
%% Mnesia database restore
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
%% mod_configure/adhoc
restore(Path) ->
mnesia:restore(Path, [{keep_tables,keep_tables()},
{default_op, skip_tables}]).
%% This function return a list of tables that should be kept from a previous
%% version backup.
%% Obsolete tables or tables created by module who are no longer used are not
%% restored and are ignored.
keep_tables() ->
lists:flatten([acl, passwd, config, local_config, disco_publish,
keep_modules_tables()]).
%% Returns the list of modules tables in use, according to the list of actually
%% loaded modules
keep_modules_tables() ->
lists:map(fun(Module) -> module_tables(Module) end,
gen_mod:loaded_modules(?MYNAME)).
%% TODO: This mapping should probably be moved to a callback function in each
%% module.
%% Mapping between modules and their tables
module_tables(mod_announce) -> [motd, motd_users];
module_tables(mod_irc) -> [irc_custom];
module_tables(mod_last) -> [last_activity];
module_tables(mod_muc) -> [muc_room, muc_registered];
module_tables(mod_offline) -> [offline_msg];
module_tables(mod_privacy) -> [privacy];
module_tables(mod_private) -> [private_storage];
module_tables(mod_pubsub) -> [pubsub_node];
module_tables(mod_roster) -> [roster];
module_tables(mod_shared_roster) -> [sr_group, sr_user];
module_tables(mod_vcard) -> [vcard, vcard_search];
module_tables(_Other) -> [].
+1
View File
@@ -24,6 +24,7 @@ start(normal, _Args) ->
catch ssl:start(),
translate:start(),
acl:start(),
ejabberd_ctl:init(),
gen_mod:start(),
ejabberd_config:start(),
Sup = ejabberd_sup:start_link(),
+111 -31
View File
@@ -3,9 +3,13 @@
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Authentification
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@sevcom.net>
%%% Updated : 23 Feb 2006 by Mickael Remond <mremond@process-one.net>
%%% for anonymous login support
%%% Id : $Id$
%%%----------------------------------------------------------------------
%% TODO: Use the functions in ejabberd auth to add and remove users.
-module(ejabberd_auth).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
@@ -21,76 +25,152 @@
get_password/2,
get_password_s/2,
is_user_exists/2,
is_user_exists_in_other_modules/3,
remove_user/2,
remove_user/3,
plain_password_required/1
plain_password_required/1,
ctl_process_get_registered/3
]).
-export([auth_modules/1]).
-include("ejabberd.hrl").
-include("ejabberd_ctl.hrl").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start() ->
lists:foreach(fun(Host) ->
(auth_module(Host)):start(Host)
end, ?MYHOSTS).
lists:foreach(
fun(Host) ->
lists:foreach(
fun(M) ->
M:start(Host)
end, auth_modules(Host))
end, ?MYHOSTS).
plain_password_required(Server) ->
(auth_module(Server)):plain_password_required().
lists:any(
fun(M) ->
M:plain_password_required()
end, auth_modules(Server)).
check_password(User, Server, Password) ->
(auth_module(Server)):check_password(User, Server, Password).
lists:any(
fun(M) ->
M:check_password(User, Server, Password)
end, auth_modules(Server)).
check_password(User, Server, Password, StreamID, Digest) ->
(auth_module(Server)):check_password(User, Server, Password, StreamID, Digest).
lists:any(
fun(M) ->
M:check_password(User, Server, Password, StreamID, Digest)
end, auth_modules(Server)).
set_password(User, Server, Password) ->
(auth_module(Server)):set_password(User, Server, Password).
lists:foldl(
fun(M, {error, _}) ->
M:set_password(User, Server, Password);
(_M, Res) ->
Res
end, {error, not_allowed}, auth_modules(Server)).
try_register(User, Server, Password) ->
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
case is_user_exists(User,Server) of
true ->
(auth_module(Server)):try_register(User, Server, Password);
{atomic, exists};
false ->
{error, not_allowed}
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
true ->
lists:foldl(
fun(_M, {atomic, ok} = Res) ->
Res;
(M, _) ->
M:try_register(User, Server, Password)
end, {error, not_allowed}, auth_modules(Server));
false ->
{error, not_allowed}
end
end.
%% Registered users list do not include anonymous users logged
dirty_get_registered_users() ->
(auth_module(?MYNAME)):dirty_get_registered_users().
lists:flatmap(
fun(M) ->
M:dirty_get_registered_users()
end, auth_modules(?MYNAME)).
%% Registered users list do not include anonymous users logged
get_vh_registered_users(Server) ->
(auth_module(Server)):get_vh_registered_users(Server).
lists:flatmap(
fun(M) ->
M:get_vh_registered_users(Server)
end, auth_modules(Server)).
get_password(User, Server) ->
(auth_module(Server)):get_password(User, Server).
lists:foldl(
fun(M, false) ->
M:get_password(User, Server);
(_M, Password) ->
Password
end, false, auth_modules(Server)).
get_password_s(User, Server) ->
(auth_module(Server)):get_password_s(User, Server).
case get_password(User, Server) of
false ->
"";
Password ->
Password
end.
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
is_user_exists(User, Server) ->
(auth_module(Server)):is_user_exists(User, Server).
lists:any(
fun(M) ->
M:is_user_exists(User, Server)
end, auth_modules(Server)).
%% Check if the user exists in all authentications module except the module
%% passed as parameter
is_user_exists_in_other_modules(Module, User, Server) ->
lists:any(
fun(M) ->
M:is_user_exists(User, Server)
end, auth_modules(Server)--[Module]).
remove_user(User, Server) ->
(auth_module(Server)):remove_user(User, Server).
lists:foreach(
fun(M) ->
M:remove_user(User, Server)
end, auth_modules(Server)).
remove_user(User, Server, Password) ->
(auth_module(Server)):remove_user(User, Server, Password).
lists:foreach(
fun(M) ->
M:remove_user(User, Server, Password)
end, auth_modules(Server)).
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
NewLine = io_lib:format("~n", []),
SUsers = lists:sort(Users),
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
io:format("~s", [FUsers]),
{stop, ?STATUS_SUCCESS};
ctl_process_get_registered(Val, _Host, _Args) ->
Val.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
auth_module(Server) ->
auth_modules(Server) ->
LServer = jlib:nameprep(Server),
case ejabberd_config:get_local_option({auth_method, LServer}) of
external ->
ejabberd_auth_external;
ldap ->
ejabberd_auth_ldap;
odbc ->
ejabberd_auth_odbc;
_ ->
ejabberd_auth_internal
end.
Method = ejabberd_config:get_local_option({auth_method, LServer}),
Methods = if
Method == undefined -> [];
is_list(Method) -> Method;
is_atom(Method) -> [Method]
end,
[list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods].
+221
View File
@@ -0,0 +1,221 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_auth_anonymous.erl
%%% Author : Mickael Remond <mickael.remond@process-one.net>
%%% Purpose : Anonymous feature support in ejabberd
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%% Anonymous support is based on the work of Magnus Henoch
%%% <henoch@dtek.chalmers.se> and heavily extended by Process-one.
%%%
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_auth_anonymous).
-author('mickael.remond@process-one.net').
-vsn('$Revision$ ').
-export([start/1,
allow_anonymous/1,
is_sasl_anonymous_enabled/1,
is_login_anonymous_enabled/1,
anonymous_user_exist/2,
allow_multiple_connections/1,
register_connection/2,
unregister_connection/2
]).
%% Function used by ejabberd_auth:
-export([login/2,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_password/2,
get_password/3,
is_user_exists/2,
remove_user/2,
remove_user/3,
plain_password_required/0]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(anonymous, {us, sid}).
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
start(Host) ->
%% TODO: Check cluster mode
mnesia:create_table(anonymous, [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, anonymous)}]),
%% The hooks are needed to add / remove users from the anonymous tables
ejabberd_hooks:add(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
?MODULE, unregister_connection, 100),
ok.
%% Return true if anonymous is allowed for host or false otherwise
allow_anonymous(Host) ->
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
%% Return true if anonymous mode is enabled and if anonymous protocol is SASL
%% anonymous protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
case anonymous_protocol(Host) of
sasl_anon -> true;
both -> true;
_Other -> false
end
end.
%% Return true if anonymous login is enabled on the server
%% anonymous login can be use using standard authentication method (i.e. with
%% clients that do not support anonymous login)
is_login_anonymous_enabled(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
case anonymous_protocol(Host) of
login_anon -> true;
both -> true;
_Other -> false
end
end.
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon
anonymous_protocol(Host) ->
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
sasl_anon -> sasl_anon;
login_anon -> login_anon;
both -> both;
_Other -> sasl_anon
end.
%% Return true if multiple connections have been allowed in the config file
%% defaults to false
allow_multiple_connections(Host) ->
case ejabberd_config:get_local_option({allow_multiple_connections, Host}) of
true -> true;
_Other -> false
end.
%% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({anonymous, US}) of
[] ->
false;
[_H|_T] ->
true
end.
%% Remove connection from Mnesia tables
remove_connection(SID, LUser, LServer) ->
US = {LUser, LServer},
F = fun() ->
mnesia:delete_object({anonymous, US, SID})
end,
mnesia:transaction(F).
%% Register connection
register_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
US = {LUser, LServer},
mnesia:sync_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end).
%% Remove an anonymous user from the anonymous users table
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
remove_connection(SID, LUser, LServer).
%% ---------------------------------
%% Specific anonymous auth functions
%% ---------------------------------
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
check_password(User, Server, Password) ->
check_password(User, Server, Password, undefined, undefined).
check_password(User, Server, _Password, _StreamID, _Digest) ->
%% We refuse login for registered accounts (They cannot logged but
%% they however are "reserved")
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
User, Server) of
true -> false;
false -> login(User, Server)
end.
login(User, Server) ->
case is_login_anonymous_enabled(Server) of
false -> false;
true ->
case anonymous_user_exist(User, Server) of
%% Reject the login if an anonymous user with the same login
%% is already logged and if multiple login has not been enable
%% in the config file.
true -> allow_multiple_connections(Server);
%% Accept login and add user to the anonymous table
false -> true
end
end.
%% When anonymous login is enabled, check that the user is permanent before
%% changing its password
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
true ->
ok;
false ->
{error, not_allowed}
end.
%% When anonymous login is enabled, check if permanent users are allowed on
%% the server:
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() ->
[].
get_vh_registered_users(_Server) ->
[].
%% Return password of permanent user or false for anonymous users
get_password(User, Server) ->
get_password(User, Server, "").
get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) of
%% We return the default value if the user is anonymous
true ->
DefaultValue;
%% We return the permanent user password otherwise
false ->
false
end.
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
is_user_exists(User, Server) ->
anonymous_user_exist(User, Server).
remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(_User, _Server, _Password) ->
not_allowed.
plain_password_required() ->
false.
+5 -1
View File
@@ -33,10 +33,14 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(_Host) ->
start(Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
update_table(),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
ok.
plain_password_required() ->
+303 -62
View File
@@ -10,8 +10,21 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
%% gen_server callbacks
-export([init/1,
handle_info/2,
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3
]).
%% External exports
-export([start/1,
stop/1,
start_link/1,
set_password/3,
check_password/3,
check_password/5,
@@ -29,35 +42,90 @@
-include("ejabberd.hrl").
-include("eldap/eldap.hrl").
-record(state, {host,
eldap_id,
bind_eldap_id,
servers,
port,
dn,
password,
base,
uidattr,
uidattr_format,
ufilter,
sfilter,
dn_filter,
dn_filter_attrs
}).
%% Unused callbacks.
handle_cast(_Request, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_info(_Info, State) ->
{noreply, State}.
%% -----
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
RootDN = ejabberd_config:get_local_option({ldap_rootdn, Host}),
Password = ejabberd_config:get_local_option({ldap_password, Host}),
eldap:start_link(get_eldap_id(Host, ejabberd),
LDAPServers, 389, RootDN, Password),
eldap:start_link(get_eldap_id(Host, ejabberd_bind),
LDAPServers, 389, RootDN, Password),
ok.
Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {
Proc, {?MODULE, start_link, [Host]},
permanent, 1000, worker, [?MODULE]
},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:call(Proc, stop),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
terminate(_Reason, State) ->
ejabberd_ctl:unregister_commands(
State#state.host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered).
init(Host) ->
State = parse_options(Host),
eldap:start_link(State#state.eldap_id,
State#state.servers,
State#state.port,
State#state.dn,
State#state.password),
eldap:start_link(State#state.bind_eldap_id,
State#state.servers,
State#state.port,
State#state.dn,
State#state.password),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
{ok, State}.
-define(REPLY_TIMEOUT, 10000).
plain_password_required() ->
true.
check_password(User, Server, Password) ->
case find_user_dn(User, Server) of
false ->
Proc = gen_mod:get_module_proc(Server, ?MODULE),
case catch gen_server:call(Proc,
{check_pass, User, Password}, ?REPLY_TIMEOUT) of
{'EXIT', _} ->
false;
DN ->
LServer = jlib:nameprep(Server),
case eldap:bind(get_eldap_id(LServer, ejabberd_bind),
DN, Password) of
ok ->
true;
_ ->
false
end
Result ->
Result
end.
check_password(User, Server, Password, _StreamID, _Digest) ->
@@ -73,31 +141,13 @@ dirty_get_registered_users() ->
get_vh_registered_users(?MYNAME).
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
Attr = ejabberd_config:get_local_option({ldap_uidattr, LServer}),
Filter = eldap:present(Attr),
Base = ejabberd_config:get_local_option({ldap_base, LServer}),
case eldap:search(get_eldap_id(LServer, ejabberd),
[{base, Base},
{filter, Filter},
{attributes, [Attr]}]) of
#eldap_search_result{entries = Es} ->
lists:flatmap(
fun(E) ->
case lists:keysearch(Attr, 1, E#eldap_entry.attributes) of
{value, {_, [U]}} ->
case jlib:nodeprep(U) of
error ->
[];
LU ->
[{LU, LServer}]
end;
_ ->
[]
end
end, Es);
_ ->
[]
Proc = gen_mod:get_module_proc(Server, ?MODULE),
case catch gen_server:call(Proc,
get_vh_registered_users, ?REPLY_TIMEOUT) of
{'EXIT', _} ->
[];
Result ->
Result
end.
get_password(_User, _Server) ->
@@ -107,11 +157,13 @@ get_password_s(_User, _Server) ->
"".
is_user_exists(User, Server) ->
case find_user_dn(User, Server) of
false ->
Proc = gen_mod:get_module_proc(Server, ?MODULE),
case catch gen_server:call(Proc,
{is_user_exists, User}, ?REPLY_TIMEOUT) of
{'EXIT', _} ->
false;
_DN ->
true
Result ->
Result
end.
remove_user(_User, _Server) ->
@@ -120,25 +172,214 @@ remove_user(_User, _Server) ->
remove_user(_User, _Server, _Password) ->
not_allowed.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
handle_call({check_pass, User, Password}, _From, State) ->
Reply = case find_user_dn(User, State) of
false ->
false;
DN ->
case eldap:bind(State#state.bind_eldap_id, DN, Password) of
ok -> true;
_ -> false
end
end,
{reply, Reply, State};
find_user_dn(User, Server) ->
LServer = jlib:nameprep(Server),
Attr = ejabberd_config:get_local_option({ldap_uidattr, LServer}),
Filter = eldap:equalityMatch(Attr, User),
Base = ejabberd_config:get_local_option({ldap_base, LServer}),
case eldap:search(get_eldap_id(LServer, ejabberd),
[{base, Base},
{filter, Filter},
{attributes, []}]) of
#eldap_search_result{entries = [E | _]} ->
E#eldap_entry.object_name;
handle_call(get_vh_registered_users, _From, State) ->
UA = State#state.uidattr,
UAF = State#state.uidattr_format,
Eldap_ID = State#state.eldap_id,
Server = State#state.host,
SortedDNAttrs = usort_attrs(State#state.dn_filter_attrs),
Reply = case eldap_filter:parse(State#state.sfilter) of
{ok, EldapFilter} ->
case eldap:search(Eldap_ID, [{base, State#state.base},
{filter, EldapFilter},
{attributes, SortedDNAttrs}]) of
#eldap_search_result{entries = Entries} ->
lists:flatmap(
fun(#eldap_entry{attributes = Attrs,
object_name = DN}) ->
case is_valid_dn(DN, Attrs, State) of
false -> [];
_ ->
case get_ldap_attr(UA, Attrs) of
"" -> [];
User ->
case get_user_part(User, UAF) of
{ok, U} ->
case jlib:nodeprep(U) of
error -> [];
LU -> [{LU, jlib:nameprep(Server)}]
end;
_ -> []
end
end
end
end, Entries);
_ ->
[]
end;
_ ->
[]
end,
{reply, Reply, State};
handle_call({is_user_exists, User}, _From, State) ->
Reply = case find_user_dn(User, State) of
false -> false;
_DN -> true
end,
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
find_user_dn(User, State) ->
DNAttrs = usort_attrs(State#state.dn_filter_attrs),
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
{ok, Filter} ->
case eldap:search(State#state.eldap_id, [{base, State#state.base},
{filter, Filter},
{attributes, DNAttrs}]) of
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
object_name = DN} | _]} ->
is_valid_dn(DN, Attrs, State);
_ ->
false
end;
_ ->
false
end.
get_eldap_id(Host, Name) ->
atom_to_list(gen_mod:get_module_proc(Host, Name)).
is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
DN;
is_valid_dn(DN, Attrs, State) ->
DNAttrs = State#state.dn_filter_attrs,
UA = State#state.uidattr,
UAF = State#state.uidattr_format,
Values = [{"%s", get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
SubstValues = case get_ldap_attr(UA, Attrs) of
"" -> Values;
S ->
case get_user_part(S, UAF) of
{ok, U} -> [{"%u", U} | Values];
_ -> Values
end
end ++ [{"%d", State#state.host}, {"%D", DN}],
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
{ok, EldapFilter} ->
case eldap:search(State#state.eldap_id, [
{base, State#state.base},
{filter, EldapFilter},
{attributes, ["dn"]}]) of
#eldap_search_result{entries = [_|_]} ->
DN;
_ ->
false
end;
_ ->
false
end.
%%%----------------------------------------------------------------------
%%% Auxiliary functions
%%%----------------------------------------------------------------------
get_user_part(String, Pattern) ->
F = fun(S, P) ->
First = string:str(P, "%u"),
TailLength = length(P) - (First+1),
string:sub_string(S, First, length(S) - TailLength)
end,
case catch F(String, Pattern) of
{'EXIT', _} ->
{error, badmatch};
Result ->
case regexp:sub(Pattern, "%u", Result) of
{ok, String, _} -> {ok, Result};
_ -> {error, badmatch}
end
end.
case_insensitive_match(X, Y) ->
X1 = stringprep:tolower(X),
Y1 = stringprep:tolower(Y),
if
X1 == Y1 -> true;
true -> false
end.
get_ldap_attr(LDAPAttr, Attributes) ->
Res = lists:filter(
fun({Name, _}) ->
case_insensitive_match(Name, LDAPAttr)
end, Attributes),
case Res of
[{_, [Value|_]}] -> Value;
_ -> ""
end.
usort_attrs(Attrs) when is_list(Attrs) ->
lists:usort(Attrs);
usort_attrs(_) ->
[].
parse_options(Host) ->
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
undefined -> 389;
P -> P
end,
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
undefined -> "";
RDN -> RDN
end,
Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
undefined -> "";
Pass -> Pass
end,
UIDAttr = case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
undefined -> "uid";
UA -> UA
end,
UIDAttrFormat = case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
undefined -> "%u";
UAF -> UAF
end,
SubFilter = "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")",
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
undefined -> SubFilter;
"" -> SubFilter;
F -> "(&" ++ SubFilter ++ F ++ ")"
end,
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
{DNFilter, DNFilterAttrs} =
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
undefined -> {undefined, undefined};
{DNF, DNFA} -> {DNF, DNFA}
end,
#state{host = Host,
eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID,
servers = LDAPServers,
port = LDAPPort,
dn = RootDN,
password = Password,
base = LDAPBase,
uidattr = UIDAttr,
uidattr_format = UIDAttrFormat,
ufilter = UserFilter,
sfilter = SearchFilter,
dn_filter = DNFilter,
dn_filter_attrs = DNFilterAttrs
}.
+46 -54
View File
@@ -34,7 +34,18 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
ejabberd_odbc_sup:start_link(Host),
ChildSpec =
{gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
{ejabberd_odbc_sup, start_link, [Host]},
temporary,
infinity,
supervisor,
[ejabberd_odbc_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
ok.
plain_password_required() ->
@@ -46,10 +57,8 @@ check_password(User, Server, Password) ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select password from users "
"where username='", Username, "'"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
true;
_ ->
@@ -63,10 +72,8 @@ check_password(User, Server, Password, StreamID, Digest) ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select password from users "
"where username='", Username, "'"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Passwd}]} ->
DigRes = if
Digest /= "" ->
@@ -91,12 +98,8 @@ set_password(User, Server, Password) ->
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["begin;"
"delete from users where username='", Username ,"';"
"insert into users(username, password) "
"values ('", Username, "', '", Pass, "'); commit"])
LServer = jlib:nameprep(Server),
catch odbc_queries:set_password_t(LServer, Username, Pass)
end.
@@ -107,10 +110,8 @@ try_register(User, Server, Password) ->
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["insert into users(username, password) "
"values ('", Username, "', '", Pass, "')"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:add_user(LServer, Username, Pass) of
{updated, 1} ->
{atomic, ok};
_ ->
@@ -123,9 +124,7 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
case catch ejabberd_odbc:sql_query(
LServer,
"select username from users") of
case catch odbc_queries:list_users(LServer) of
{selected, ["username"], Res} ->
[{U, LServer} || {U} <- Res];
_ ->
@@ -138,10 +137,8 @@ get_password(User, Server) ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select password from users "
"where username='", Username, "'"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
@@ -155,10 +152,8 @@ get_password_s(User, Server) ->
"";
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select password from users "
"where username='", Username, "'"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
@@ -172,10 +167,8 @@ is_user_exists(User, Server) ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["select password from users "
"where username='", Username, "'"]) of
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{_Password}]} ->
true;
_ ->
@@ -189,9 +182,8 @@ remove_user(User, Server) ->
error;
LUser ->
Username = ejabberd_odbc:escape(LUser),
catch ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["delete from users where username='", Username ,"'"]),
LServer = jlib:nameprep(Server),
catch odbc_queries:del_user(LServer, Username),
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
[User, Server])
end.
@@ -203,21 +195,21 @@ remove_user(User, Server, Password) ->
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
case catch
ejabberd_odbc:sql_query(
jlib:nameprep(Server),
["begin;"
"select password from users where username='", Username, "';"
"delete from users "
"where username='", Username, "' and password='", Pass, "';"
"commit"]) of
{selected, ["password"], [{Password}]} ->
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
[User, Server]),
ok;
{selected, ["password"], []} ->
not_exists;
_ ->
not_allowed
end
LServer = jlib:nameprep(Server),
F = fun() ->
Result = odbc_queries:del_user_return_password(
LServer, Username, Pass),
case Result of
{selected, ["password"], [{Password}]} ->
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
[User, Server]),
ok;
{selected, ["password"], []} ->
not_exists;
_ ->
not_allowed
end
end,
{atomic, Result} = odbc_queries:transaction(LServer, F),
Result
end.
+279 -71
View File
@@ -8,7 +8,7 @@
-module(ejabberd_c2s).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-update_info({update, 0}).
-behaviour(gen_fsm).
@@ -17,6 +17,7 @@
start_link/2,
send_text/2,
send_element/2,
become_controller/1,
get_presence/1]).
%% gen_fsm callbacks
@@ -45,6 +46,7 @@
sasl_state,
access,
shaper,
zlib = false,
tls = false,
tls_required = false,
tls_enabled = false,
@@ -52,6 +54,7 @@
authenticated = false,
jid,
user = "", server = ?MYNAME, resource = "",
sid,
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
@@ -97,6 +100,9 @@ start(SockData, Opts) ->
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_c2s, [SockData, Opts], ?FSMOPTS).
become_controller(Pid) ->
gen_fsm:send_all_state_event(Pid, become_controller).
%% Return Username, Resource and presence information
get_presence(FsmRef) ->
gen_fsm:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
@@ -121,6 +127,12 @@ init([{SockMod, Socket}, Opts]) ->
{value, {_, S}} -> S;
_ -> none
end,
MaxStanzaSize =
case lists:keysearch(max_stanza_size, 1, Opts) of
{value, {_, Size}} -> Size;
_ -> infinity
end,
Zlib = lists:member(zlib, Opts),
StartTLS = lists:member(starttls, Opts),
StartTLSRequired = lists:member(starttls_required, Opts),
TLSEnabled = lists:member(tls, Opts),
@@ -132,15 +144,18 @@ init([{SockMod, Socket}, Opts]) ->
if
TLSEnabled ->
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
RecPid = ejabberd_receiver:start(TLSSocket, tls, none),
RecPid = ejabberd_receiver:start(
TLSSocket, tls, none, MaxStanzaSize),
{tls, TLSSocket, RecPid};
true ->
RecPid = ejabberd_receiver:start(Socket, SockMod, none),
RecPid = ejabberd_receiver:start(
Socket, SockMod, none, MaxStanzaSize),
{SockMod, Socket, RecPid}
end,
{ok, wait_for_stream, #state{socket = Socket1,
sockmod = SockMod1,
receiver = ReceiverPid,
zlib = Zlib,
tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled,
@@ -196,10 +211,22 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
{xmlelement, "mechanism", [],
[{xmlcdata, S}]}
end, cyrsasl:listmech(Server)),
SockMod = StateData#state.sockmod,
Zlib = StateData#state.zlib,
CompressFeature =
case Zlib andalso
(SockMod == gen_tcp) of
true ->
[{xmlelement, "compression",
[{"xmlns", ?NS_FEATURE_COMPRESS}],
[{xmlelement, "method",
[], [{xmlcdata, "zlib"}]}]}];
_ ->
[]
end,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
SockMod = StateData#state.sockmod,
TLSFeature =
case (TLS == true) andalso
(TLSEnabled == false) andalso
@@ -220,7 +247,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
end,
send_element(StateData,
{xmlelement, "stream:features", [],
TLSFeature ++
TLSFeature ++ CompressFeature ++
[{xmlelement, "mechanisms",
[{"xmlns", ?NS_SASL}],
Mechs}] ++
@@ -307,18 +334,20 @@ wait_for_stream(closed, StateData) ->
wait_for_auth({xmlstreamelement, El}, StateData) ->
case is_auth_packet(El) of
{auth, _ID, get, {"", _, _, _}} ->
Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
send_element(StateData, Err),
{next_state, wait_for_auth, StateData};
{auth, _ID, get, {U, _, _, _}} ->
{xmlelement, Name, Attrs, _Els} = jlib:make_result_iq_reply(El),
case U of
"" ->
UCdata = [];
_ ->
UCdata = [{xmlcdata, U}]
end,
Res = case ejabberd_auth:plain_password_required(
StateData#state.server) of
false ->
{xmlelement, Name, Attrs,
[{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
[{xmlelement, "username", [], [{xmlcdata, U}]},
[{xmlelement, "username", [], UCdata},
{xmlelement, "password", [], []},
{xmlelement, "digest", [], []},
{xmlelement, "resource", [], []}
@@ -326,14 +355,14 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
true ->
{xmlelement, Name, Attrs,
[{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
[{xmlelement, "username", [], [{xmlcdata, U}]},
[{xmlelement, "username", [], UCdata},
{xmlelement, "password", [], []},
{xmlelement, "resource", [], []}
]}]}
end,
send_element(StateData, Res),
{next_state, wait_for_auth, StateData};
{auth, _ID, set, {U, P, D, ""}} ->
{auth, _ID, set, {_U, _P, _D, ""}} ->
Err = jlib:make_error_reply(
El,
?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)),
@@ -353,8 +382,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
"(~w) Accepted legacy authentication for ~s",
[StateData#state.socket,
jlib:jid_to_string(JID)]),
SID = {now(), self()},
ejabberd_sm:open_session(
U, StateData#state.server, R),
SID, U, StateData#state.server, R),
Res1 = jlib:make_result_iq_reply(El),
Res = setelement(4, Res1, []),
send_element(StateData, Res),
@@ -378,6 +408,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
StateData#state{user = U,
resource = R,
jid = JID,
sid = SID,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList}};
@@ -430,6 +461,7 @@ wait_for_auth(closed, StateData) ->
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, Attrs, Els} = El,
Zlib = StateData#state.zlib,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
@@ -482,7 +514,6 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
lists:keydelete(
certfile, 1, StateData#state.tls_options)]
end,
io:format("O: ~p~n", [TLSOpts]),
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(StateData#state.receiver, TLSSocket),
send_element(StateData,
@@ -493,6 +524,40 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
streamid = new_id(),
tls_enabled = true
}};
{?NS_COMPRESS, "compress"} when Zlib == true,
SockMod == gen_tcp ->
case xml:get_subtag(El, "method") of
false ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_COMPRESS}],
[{xmlelement, "setup-failed", [], []}]}),
{next_state, wait_for_feature_request, StateData};
Method ->
case xml:get_tag_cdata(Method) of
"zlib" ->
Socket = StateData#state.socket,
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(SockMod,
Socket),
ejabberd_receiver:compress(StateData#state.receiver,
ZlibSocket),
send_element(StateData,
{xmlelement, "compressed",
[{"xmlns", ?NS_COMPRESS}], []}),
{next_state, wait_for_stream,
StateData#state{sockmod = ejabberd_zlib,
socket = ZlibSocket,
streamid = new_id()
}};
_ ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_COMPRESS}],
[{xmlelement, "unsupported-method",
[], []}]}),
{next_state, wait_for_feature_request, StateData}
end
end;
_ ->
if
(SockMod == gen_tcp) and TLSRequired ->
@@ -631,8 +696,9 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Opened session for ~s",
[StateData#state.socket,
jlib:jid_to_string(JID)]),
SID = {now(), self()},
ejabberd_sm:open_session(
U, StateData#state.server, R),
SID, U, StateData#state.server, R),
Res = jlib:make_result_iq_reply(El),
send_element(StateData, Res),
change_shaper(StateData, JID),
@@ -651,7 +717,8 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
PL -> PL
end,
{next_state, session_established,
StateData#state{pres_f = ?SETS:from_list(Fs1),
StateData#state{sid = SID,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList}};
_ ->
@@ -693,15 +760,16 @@ session_established({xmlstreamelement, El}, StateData) ->
_ ->
jlib:string_to_jid(To)
end,
NewEl1 = jlib:remove_attr("xmlns", El),
NewEl = case xml:get_attr_s("xml:lang", Attrs) of
"" ->
case StateData#state.lang of
"" -> El;
"" -> NewEl1;
Lang ->
xml:replace_tag_attr("xml:lang", Lang, El)
xml:replace_tag_attr("xml:lang", Lang, NewEl1)
end;
_ ->
El
NewEl1
end,
NewState =
case ToJID of
@@ -791,6 +859,12 @@ session_established(closed, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(become_controller, StateName, StateData) ->
ok = (StateData#state.sockmod):controlling_process(
StateData#state.socket,
StateData#state.receiver),
ejabberd_receiver:become_controller(StateData#state.receiver),
{next_state, StateName, StateData};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -845,8 +919,38 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
"presence" ->
case xml:get_attr_s("type", Attrs) of
"probe" ->
process_presence_probe(From, To, StateData),
{false, Attrs, StateData};
LFrom = jlib:jid_tolower(From),
LBFrom = jlib:jid_remove_resource(LFrom),
NewStateData =
case ?SETS:is_element(
LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
LBFrom, StateData#state.pres_a) of
true ->
StateData;
false ->
case ?SETS:is_element(
LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
StateData#state.pres_a),
StateData#state{pres_a = A};
false ->
case ?SETS:is_element(
LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
StateData#state.pres_a),
StateData#state{pres_a = A};
false ->
StateData
end
end
end,
process_presence_probe(From, To, NewStateData),
{false, Attrs, NewStateData};
"error" ->
NewA = remove_element(jlib:jid_tolower(From),
StateData#state.pres_a),
@@ -873,7 +977,37 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
{'EXIT', _Reason} ->
{true, Attrs, StateData};
allow ->
{true, Attrs, StateData};
LFrom = jlib:jid_tolower(From),
LBFrom = jlib:jid_remove_resource(LFrom),
case ?SETS:is_element(
LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
LBFrom, StateData#state.pres_a) of
true ->
{true, Attrs, StateData};
false ->
case ?SETS:is_element(
LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
StateData#state.pres_a),
{true, Attrs,
StateData#state{pres_a = A}};
false ->
case ?SETS:is_element(
LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
StateData#state.pres_a),
{true, Attrs,
StateData#state{pres_a = A}};
false ->
{true, Attrs, StateData}
end
end
end;
deny ->
{false, Attrs, StateData}
end
@@ -890,16 +1024,30 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
StateData)};
[{exit, Reason}] ->
{exit, Attrs, Reason};
[{privacy_list, PrivList}] ->
{false, Attrs,
[{privacy_list, PrivList, PrivListName}] ->
case catch mod_privacy:updated_list(
StateData#state.privacy_list,
PrivList) of
{'EXIT', _} ->
{false, Attrs, StateData};
NewPL ->
StateData#state{privacy_list = NewPL}
end};
PrivPushIQ =
#iq{type = set, xmlns = ?NS_PRIVACY,
id = "push",
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_PRIVACY}],
[{xmlelement, "list",
[{"name", PrivListName}],
[]}]}]},
PrivPushEl =
jlib:replace_from_to(
jlib:jid_remove_resource(
StateData#state.jid),
StateData#state.jid,
jlib:iq_to_xml(PrivPushIQ)),
send_element(StateData, PrivPushEl),
{false, Attrs, StateData#state{privacy_list = NewPL}}
end;
_ ->
{false, Attrs, StateData}
end;
@@ -961,6 +1109,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
end,
if
Pass == exit ->
catch send_text(StateData, ?STREAM_TRAILER),
{stop, normal, StateData};
Pass ->
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
@@ -975,7 +1124,10 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
{next_state, StateName, NewState};
true ->
{next_state, StateName, NewState}
end.
end;
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
@@ -995,10 +1147,12 @@ terminate(_Reason, StateName, StateData) ->
[{"type", "unavailable"}],
[{xmlelement, "status", [],
[{xmlcdata, "Replaced by new connection"}]}]},
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
"Replaced by new connection"),
ejabberd_sm:close_session_unset_presence(
StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
"Replaced by new connection"),
presence_broadcast(
StateData, From, StateData#state.pres_a, Packet),
presence_broadcast(
@@ -1007,25 +1161,27 @@ terminate(_Reason, StateName, StateData) ->
?INFO_MSG("(~w) Close session for ~s",
[StateData#state.socket,
jlib:jid_to_string(StateData#state.jid)]),
ejabberd_sm:close_session(StateData#state.user,
StateData#state.server,
StateData#state.resource),
Tmp = ?SETS:new(),
EmptySet = ?SETS:new(),
case StateData of
#state{pres_last = undefined,
pres_a = Tmp,
pres_i = Tmp,
pres_a = EmptySet,
pres_i = EmptySet,
pres_invis = false} ->
ok;
ejabberd_sm:close_session(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource);
_ ->
From = StateData#state.jid,
Packet = {xmlelement, "presence",
[{"type", "unavailable"}], []},
ejabberd_sm:unset_presence(StateData#state.user,
StateData#state.server,
StateData#state.resource,
""),
ejabberd_sm:close_session_unset_presence(
StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
""),
presence_broadcast(
StateData, From, StateData#state.pres_a, Packet),
presence_broadcast(
@@ -1035,7 +1191,7 @@ terminate(_Reason, StateName, StateData) ->
_ ->
ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ejabberd_receiver:close(StateData#state.receiver),
ok.
%%%----------------------------------------------------------------------
@@ -1048,7 +1204,8 @@ change_shaper(StateData, JID) ->
ejabberd_receiver:change_shaper(StateData#state.receiver, Shaper).
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket, Text).
?DEBUG("Send XML on stream = ~p", [lists:flatten(Text)]),
catch (StateData#state.sockmod):send(StateData#state.socket, Text).
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
@@ -1147,7 +1304,8 @@ presence_update(From, Packet, StateData) ->
StatusTag ->
xml:get_tag_cdata(StatusTag)
end,
ejabberd_sm:unset_presence(StateData#state.user,
ejabberd_sm:unset_presence(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
Status),
@@ -1189,7 +1347,14 @@ presence_update(From, Packet, StateData) ->
"unsubscribed" ->
StateData;
_ ->
update_priority(xml:get_subtag(Packet, "priority"), StateData),
OldPriority = case StateData#state.pres_last of
undefined ->
0;
OldPresence ->
get_priority_from_presence(OldPresence)
end,
NewPriority = get_priority_from_presence(Packet),
update_priority(NewPriority, StateData),
FromUnavail = (StateData#state.pres_last == undefined) or
StateData#state.pres_invis,
?DEBUG("from unavail = ~p~n", [FromUnavail]),
@@ -1200,6 +1365,7 @@ presence_update(From, Packet, StateData) ->
StateData#state.server,
[StateData#state.jid]),
resend_offline_messages(StateData),
resend_subscription_requests(StateData),
presence_broadcast_first(
From, StateData#state{pres_last = Packet,
pres_invis = false
@@ -1210,6 +1376,11 @@ presence_update(From, Packet, StateData) ->
StateData#state.pres_f,
StateData#state.pres_a,
Packet),
if OldPriority < 0, NewPriority >= 0 ->
resend_offline_messages(StateData);
true ->
ok
end,
StateData#state{pres_last = Packet,
pres_invis = false
}
@@ -1236,28 +1407,28 @@ presence_track(From, To, Packet, StateData) ->
StateData#state{pres_i = I,
pres_a = A};
"subscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, subscribe]),
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
StateData;
"subscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, subscribed]),
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
StateData;
"unsubscribe" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, unsubscribe]),
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
StateData;
"unsubscribed" ->
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, unsubscribed]),
ejabberd_router:route(jlib:jid_remove_resource(From), To, Packet),
StateData;
"error" ->
ejabberd_router:route(From, To, Packet),
@@ -1389,6 +1560,7 @@ roster_change(IJID, ISubscription, StateData) ->
LIJID = jlib:jid_tolower(IJID),
IsFrom = (ISubscription == both) or (ISubscription == from),
IsTo = (ISubscription == both) or (ISubscription == to),
OldIsFrom = ?SETS:is_element(LIJID, StateData#state.pres_f),
FSet = if
IsFrom ->
?SETS:add_element(LIJID, StateData#state.pres_f);
@@ -1407,14 +1579,26 @@ roster_change(IJID, ISubscription, StateData) ->
P ->
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
From = StateData#state.jid,
Cond1 = (not StateData#state.pres_invis) and IsFrom,
Cond2 = (not IsFrom)
To = jlib:make_jid(IJID),
Cond1 = (not StateData#state.pres_invis) and IsFrom
and (not OldIsFrom),
Cond2 = (not IsFrom) and OldIsFrom
and (?SETS:is_element(LIJID, StateData#state.pres_a) or
?SETS:is_element(LIJID, StateData#state.pres_i)),
if
Cond1 ->
?DEBUG("C1: ~p~n", [LIJID]),
ejabberd_router:route(From, jlib:make_jid(IJID), P),
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, P},
out) of
deny ->
ok;
_ ->
ejabberd_router:route(From, To, P)
end,
A = ?SETS:add_element(LIJID,
StateData#state.pres_a),
StateData#state{pres_a = A,
@@ -1422,9 +1606,19 @@ roster_change(IJID, ISubscription, StateData) ->
pres_t = TSet};
Cond2 ->
?DEBUG("C2: ~p~n", [LIJID]),
ejabberd_router:route(From, jlib:make_jid(IJID),
{xmlelement, "presence",
[{"type", "unavailable"}], []}),
PU = {xmlelement, "presence",
[{"type", "unavailable"}], []},
case catch mod_privacy:check_packet(
StateData#state.user,
StateData#state.server,
StateData#state.privacy_list,
{From, To, PU},
out) of
deny ->
ok;
_ ->
ejabberd_router:route(From, To, PU)
end,
I = remove_element(LIJID,
StateData#state.pres_i),
A = remove_element(LIJID,
@@ -1439,24 +1633,25 @@ roster_change(IJID, ISubscription, StateData) ->
end.
update_priority(El, StateData) ->
Pri = case El of
false ->
0;
_ ->
case catch list_to_integer(xml:get_tag_cdata(El)) of
P when is_integer(P) ->
P;
_ ->
0
end
end,
ejabberd_sm:set_presence(StateData#state.user,
update_priority(Pri, StateData) ->
ejabberd_sm:set_presence(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
Pri).
get_priority_from_presence(PresencePacket) ->
case xml:get_subtag(PresencePacket, "priority") of
false ->
0;
SubEl ->
case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
P when is_integer(P) ->
P;
_ ->
0
end
end.
process_privacy_iq(From, To,
#iq{type = Type, sub_el = SubEl} = IQ,
@@ -1534,6 +1729,19 @@ resend_offline_messages(#state{user = User,
end, Rs)
end.
resend_subscription_requests(#state{user = User,
server = Server} = StateData) ->
PendingSubscriptions = ejabberd_hooks:run_fold(
resend_subscription_requests_hook,
Server,
[],
[User, Server]),
lists:foreach(fun(XMLPacket) ->
send_element(StateData,
XMLPacket)
end,
PendingSubscriptions).
get_showtag(undefined) ->
"unavailable";
get_showtag(Presence) ->
@@ -1561,7 +1769,7 @@ process_unauthenticated_stanza(StateData, El) ->
% The only reasonable IQ's here are auth and register IQ's
% They contain secrets, so don't include subelements to response
ResIQ = IQ#iq{type = error,
sub_el = [?ERR_FEATURE_NOT_IMPLEMENTED]},
sub_el = [?ERR_SERVICE_UNAVAILABLE]},
Res1 = jlib:replace_from_to(
jlib:make_jid("", StateData#state.server, ""),
jlib:make_jid("", "", ""),
+215 -160
View File
@@ -8,258 +8,314 @@
-module(ejabberd_ctl).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start/0, dump_to_textfile/1]).
-export([start/0,
init/0,
process/1,
register_commands/3,
register_commands/4,
unregister_commands/3,
unregister_commands/4]).
-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2).
-define(STATUS_BADRPC, 3).
-include("ejabberd_ctl.hrl").
-include("ejabberd.hrl").
start() ->
case init:get_plain_arguments() of
[SNode | Args] ->
Node = list_to_atom(SNode),
Status = process(Node, Args),
Status = case rpc:call(Node, ?MODULE, process, [Args]) of
{badrpc, Reason} ->
io:format("RPC failed on the node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
S ->
S
end,
halt(Status);
_ ->
print_usage(),
halt(?STATUS_USAGE)
end.
init() ->
ets:new(ejabberd_ctl_cmds, [named_table, set, public]),
ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]).
process(Node, ["status"]) ->
case rpc:call(Node, init, get_status, []) of
{badrpc, Reason} ->
io:format("Can't get node ~p status: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
{InternalStatus, ProvidedStatus} ->
io:format("Node ~p is ~p. Status: ~p~n",
[Node, InternalStatus, ProvidedStatus]),
?STATUS_SUCCESS
process(["status"]) ->
{InternalStatus, ProvidedStatus} = init:get_status(),
io:format("Node ~p is ~p. Status: ~p~n",
[node(), InternalStatus, ProvidedStatus]),
case lists:keysearch(ejabberd, 1, application:which_applications()) of
false ->
io:format("ejabberd is not running~n", []),
?STATUS_ERROR;
{value,_Version} ->
io:format("ejabberd is running~n", []),
?STATUS_SUCCESS
end;
process(Node, ["stop"]) ->
case rpc:call(Node, init, stop, []) of
{badrpc, Reason} ->
io:format("Can't stop node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(["stop"]) ->
init:stop(),
?STATUS_SUCCESS;
process(Node, ["restart"]) ->
case rpc:call(Node, init, restart, []) of
{badrpc, Reason} ->
io:format("Can't restart node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(["restart"]) ->
init:restart(),
?STATUS_SUCCESS;
process(Node, ["reopen-log"]) ->
case rpc:call(Node, ejabberd_logger_h, reopen_log, []) of
{badrpc, Reason} ->
io:format("Can't reopen node ~p log: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(["reopen-log"]) ->
ejabberd_logger_h:reopen_log(),
?STATUS_SUCCESS;
process(Node, ["register", User, Server, Password]) ->
case rpc:call(Node, ejabberd_auth, try_register, [User, Server, Password]) of
process(["register", User, Server, Password]) ->
case ejabberd_auth:try_register(User, Server, Password) of
{atomic, ok} ->
?STATUS_SUCCESS;
{atomic, exists} ->
io:format("User ~p already registered at node ~p~n",
[User ++ "@" ++ Server, Node]),
[User ++ "@" ++ Server, node()]),
?STATUS_ERROR;
{error, Reason} ->
io:format("Can't register user ~p at node ~p: ~p~n",
[User ++ "@" ++ Server, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't register user ~p at node ~p: ~p~n",
[User ++ "@" ++ Server, Node, Reason]),
?STATUS_BADRPC
[User ++ "@" ++ Server, node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["unregister", User, Server]) ->
case rpc:call(Node, ejabberd_auth, remove_user, [User, Server]) of
process(["unregister", User, Server]) ->
case ejabberd_auth:remove_user(User, Server) of
{error, Reason} ->
io:format("Can't unregister user ~p at node ~p: ~p~n",
[User ++ "@" ++ Server, Node, Reason]),
[User ++ "@" ++ Server, node(), Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't unregister user ~p at node ~p: ~p~n",
[User ++ "@" ++ Server, Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(Node, ["backup", Path]) ->
case rpc:call(Node, mnesia, backup, [Path]) of
process(["backup", Path]) ->
case mnesia:backup(Path) of
ok ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't store backup in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't store backup in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["dump", Path]) ->
case rpc:call(Node, ejabberd_ctl, dump_to_textfile, [Path]) of
process(["dump", Path]) ->
case dump_to_textfile(Path) of
ok ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't store dump in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't store dump in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["load", Path]) ->
case rpc:call(Node, mnesia, load_textfile, [Path]) of
process(["load", Path]) ->
case mnesia:load_textfile(Path) of
{atomic, ok} ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't load dump in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't load dump in ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["restore", Path]) ->
case rpc:call(Node,
mnesia, restore, [Path, [{default_op, keep_tables}]]) of
process(["restore", Path]) ->
case ejabberd_admin:restore(Path) of
{atomic, _} ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't restore backup from ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't restore backup from ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
{aborted,{no_exists,Table}} ->
io:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.~n",
[filename:absname(Path), node(), Table]),
?STATUS_ERROR;
{aborted,enoent} ->
io:format("Can't restore backup from ~p at node ~p: File not found.~n",
[filename:absname(Path), node()]),
?STATUS_ERROR
end;
process(Node, ["install-fallback", Path]) ->
case rpc:call(Node, mnesia, install_fallback, [Path]) of
process(["install-fallback", Path]) ->
case mnesia:install_fallback(Path) of
ok ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't install fallback from ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't install fallback from ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["import-file", Path]) ->
case rpc:call(Node, jd2ejd, import_file, [Path]) of
process(["import-file", Path]) ->
case jd2ejd:import_file(Path) of
ok ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["import-dir", Path]) ->
case rpc:call(Node, jd2ejd, import_dir, [Path]) of
process(["import-dir", Path]) ->
case jd2ejd:import_dir(Path) of
ok ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p~n",
[filename:absname(Path), Node, Reason]),
?STATUS_BADRPC
[filename:absname(Path), node(), Reason]),
?STATUS_ERROR
end;
process(Node, ["registered-users"]) ->
case rpc:call(Node, ejabberd_auth, dirty_get_registered_users, []) of
Users when is_list(Users) ->
NewLine = io_lib:format("~n", []),
SUsers = lists:sort(Users),
FUsers = lists:map(fun({U, S}) -> [U, $@, S, NewLine] end, SUsers),
io:format("~s", [FUsers]),
process(["delete-expired-messages"]) ->
mod_offline:remove_expired_messages(),
?STATUS_SUCCESS;
process(["delete-old-messages", Days]) ->
case catch list_to_integer(Days) of
{'EXIT',{Reason, _Stack}} ->
io:format("Can't delete old messages (~p). Please pass an integer as parameter.~n",
[Reason]),
?STATUS_ERROR;
Integer when Integer >= 0 ->
{atomic, _} = mod_offline:remove_old_messages(Integer),
io:format("Removed messages older than ~s days~n", [Days]),
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't get list of registered users at node ~p: ~p~n",
[Node, Reason]),
Integer ->
io:format("Can't delete old messages. Please pass a positive integer as parameter.~n", []),
?STATUS_ERROR
end;
process(["vhost", H | Args]) ->
case jlib:nameprep(H) of
false ->
io:format("Bad hostname: ~p~n", [H]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't get list of registered users at node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC
Host ->
case ejabberd_hooks:run_fold(
ejabberd_ctl_process, Host, false, [Host, Args]) of
false ->
print_vhost_usage(Host),
?STATUS_USAGE;
Status ->
Status
end
end;
process(Node, ["delete-expired-messages"]) ->
case rpc:call(Node, mod_offline, remove_expired_messages, []) of
{badrpc, Reason} ->
io:format("Can't delete expired messages at node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(_Node, _Args) ->
print_usage(),
?STATUS_USAGE.
process(Args) ->
case ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
false ->
print_usage(),
?STATUS_USAGE;
Status ->
Status
end.
print_usage() ->
CmdDescs =
[{"status", "get ejabberd status"},
{"stop", "stop ejabberd"},
{"restart", "restart ejabberd"},
{"reopen-log", "reopen log file"},
{"register user server password", "register a user"},
{"unregister user server", "unregister a user"},
{"backup file", "store a database backup to file"},
{"restore file", "restore a database backup from file"},
{"install-fallback file", "install a database fallback from file"},
{"dump file", "dump a database to a text file"},
{"load file", "restore a database from a text file"},
{"import-file file", "import user data from jabberd 1.4 spool file"},
{"import-dir dir", "import user data from jabberd 1.4 spool directory"},
{"delete-expired-messages", "delete expired offline messages from database"},
{"delete-old-messages n", "delete offline messages older than n days from database"},
{"vhost host ...", "execute host-specific commands"}] ++
ets:tab2list(ejabberd_ctl_cmds),
MaxCmdLen =
lists:max(lists:map(
fun({Cmd, _Desc}) ->
length(Cmd)
end, CmdDescs)),
NewLine = io_lib:format("~n", []),
FmtCmdDescs =
lists:map(
fun({Cmd, Desc}) ->
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
Desc, NewLine]
end, CmdDescs),
io:format(
"Usage: ejabberdctl node command~n"
"~n"
"Available commands:~n"
" status\t\t\tget ejabberd status~n"
" stop\t\t\t\tstop ejabberd~n"
" restart\t\t\trestart ejabberd~n"
" reopen-log\t\t\treopen log file~n"
" register user server password\tregister a user~n"
" unregister user server\tunregister a user~n"
" backup file\t\t\tstore a database backup to file~n"
" restore file\t\t\trestore a database backup from file~n"
" install-fallback file\t\tinstall a database fallback from file~n"
" dump file\t\t\tdump a database to a text file~n"
" load file\t\t\trestore a database from a text file~n"
" import-file file\t\timport user data from jabberd 1.4 spool file~n"
" import-dir dir\t\timport user data from jabberd 1.4 spool directory~n"
" registered-users\t\tlist all registered users~n"
" delete-expired-messages\tdelete expired offline messages from database~n"
++ FmtCmdDescs ++
"~n"
"Example:~n"
" ejabberdctl ejabberd@host restart~n"
).
print_vhost_usage(Host) ->
CmdDescs =
ets:select(ejabberd_ctl_host_cmds,
[{{Host, '$1', '$2'}, [], [{{'$1', '$2'}}]}]),
MaxCmdLen =
if
CmdDescs == [] ->
0;
true ->
lists:max(lists:map(
fun({Cmd, _Desc}) ->
length(Cmd)
end, CmdDescs))
end,
NewLine = io_lib:format("~n", []),
FmtCmdDescs =
lists:map(
fun({Cmd, Desc}) ->
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
Desc, NewLine]
end, CmdDescs),
io:format(
"Usage: ejabberdctl node vhost host command~n"
"~n"
"Available commands:~n"
++ FmtCmdDescs ++
"~n"
).
register_commands(CmdDescs, Module, Function) ->
ets:insert(ejabberd_ctl_cmds, CmdDescs),
ejabberd_hooks:add(ejabberd_ctl_process,
Module, Function, 50),
ok.
register_commands(Host, CmdDescs, Module, Function) ->
ets:insert(ejabberd_ctl_host_cmds,
[{Host, Cmd, Desc} || {Cmd, Desc} <- CmdDescs]),
ejabberd_hooks:add(ejabberd_ctl_process, Host,
Module, Function, 50),
ok.
unregister_commands(CmdDescs, Module, Function) ->
lists:foreach(fun(CmdDesc) ->
ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
end, CmdDescs),
ejabberd_hooks:delete(ejabberd_ctl_process,
Module, Function, 50),
ok.
unregister_commands(Host, CmdDescs, Module, Function) ->
lists:foreach(fun({Cmd, Desc}) ->
ets:delete_object(ejabberd_ctl_host_cmds,
{Host, Cmd, Desc})
end, CmdDescs),
ejabberd_hooks:delete(ejabberd_ctl_process,
Module, Function, 50),
ok.
dump_to_textfile(File) ->
dump_to_textfile(mnesia:system_info(is_running), file:open(File, write)).
dump_to_textfile(yes, {ok, F}) ->
@@ -286,11 +342,10 @@ dump_to_textfile(_, {ok, F}) ->
dump_to_textfile(_, {error, Reason}) ->
{error, Reason}.
dump_tab(F, T) ->
W = mnesia:table_info(T, wild_pattern),
{atomic,All} = mnesia:transaction(
fun() -> mnesia:match_object(T, W, read) end),
lists:foreach(
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
+12
View File
@@ -0,0 +1,12 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_ctl.hrl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 13 Feb 2006 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2).
-define(STATUS_BADRPC, 3).
+2
View File
@@ -99,6 +99,7 @@ accept(ListenSocket, Module, Opts) ->
{error, _Reason} ->
gen_tcp:close(Socket)
end,
Module:become_controller(Pid),
accept(ListenSocket, Module, Opts);
{error, Reason} ->
?INFO_MSG("(~w) Failed TCP accept: ~w",
@@ -146,6 +147,7 @@ accept_ssl(ListenSocket, Module, Opts) ->
end,
{ok, Pid} = Module:start({ssl, Socket}, Opts),
catch ssl:controlling_process(Socket, Pid),
Module:become_controller(Pid),
accept_ssl(ListenSocket, Module, Opts);
{error, timeout} ->
accept_ssl(ListenSocket, Module, Opts);
+161 -91
View File
@@ -10,7 +10,10 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start_link/0, init/0]).
-behaviour(gen_server).
%% API
-export([start_link/0]).
-export([route/3,
register_iq_handler/4,
@@ -20,102 +23,26 @@
bounce_resource_packet/3
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {}).
-define(IQTABLE, local_iqtable).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
register(ejabberd_local,
Pid = proc_lib:spawn_link(ejabberd_local, init, [])),
{ok, Pid}.
init() ->
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
loop().
loop() ->
receive
{route, From, To, Packet} ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
loop();
{register_iq_handler, Host, XMLNS, Module, Function} ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
catch mod_disco:register_feature(Host, XMLNS),
loop();
{register_iq_handler, Host, XMLNS, Module, Function, Opts} ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
catch mod_disco:register_feature(Host, XMLNS),
loop();
{unregister_iq_handler, Host, XMLNS} ->
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ ->
ok
end,
ets:delete(?IQTABLE, {XMLNS, Host}),
catch mod_disco:unregister_feature(Host, XMLNS),
loop();
refresh_iq_handlers ->
lists:foreach(
fun(T) ->
case T of
{{XMLNS, Host}, _Module, _Function, _Opts} ->
catch mod_disco:register_feature(Host, XMLNS);
{{XMLNS, Host}, _Module, _Function} ->
catch mod_disco:register_feature(Host, XMLNS);
_ ->
ok
end
end, ets:tab2list(?IQTABLE)),
loop();
_ ->
loop()
end.
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
if
To#jid.luser /= "" ->
ejabberd_sm:route(From, To, Packet);
To#jid.lresource == "" ->
{xmlelement, Name, Attrs, _Els} = Packet,
case Name of
"iq" ->
process_iq(From, To, Packet);
"message" ->
ok;
"presence" ->
ok;
_ ->
ok
end;
true ->
{xmlelement, Name, Attrs, _Els} = Packet,
case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
_ ->
ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver,
[From, To, Packet])
end
end.
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
@@ -173,3 +100,146 @@ bounce_resource_packet(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
ejabberd_router:route(To, From, Err),
stop.
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
catch mod_disco:register_feature(Host, XMLNS),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
catch mod_disco:register_feature(Host, XMLNS),
{noreply, State};
handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ ->
ok
end,
ets:delete(?IQTABLE, {XMLNS, Host}),
catch mod_disco:unregister_feature(Host, XMLNS),
{noreply, State};
handle_info(refresh_iq_handlers, State) ->
lists:foreach(
fun(T) ->
case T of
{{XMLNS, Host}, _Module, _Function, _Opts} ->
catch mod_disco:register_feature(Host, XMLNS);
{{XMLNS, Host}, _Module, _Function} ->
catch mod_disco:register_feature(Host, XMLNS);
_ ->
ok
end
end, ets:tab2list(?IQTABLE)),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
if
To#jid.luser /= "" ->
ejabberd_sm:route(From, To, Packet);
To#jid.lresource == "" ->
{xmlelement, Name, _Attrs, _Els} = Packet,
case Name of
"iq" ->
process_iq(From, To, Packet);
"message" ->
ok;
"presence" ->
ok;
_ ->
ok
end;
true ->
{xmlelement, _Name, Attrs, _Els} = Packet,
case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
_ ->
ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver,
[From, To, Packet])
end
end.
+26 -11
View File
@@ -29,7 +29,7 @@
%% Other
%%----------------------------------------------------------------------
init(File) ->
case file:open(File, [append]) of
case file:open(File, [append, raw]) of
{ok, Fd} ->
{ok, #state{fd = Fd, file = File}};
Error ->
@@ -66,7 +66,8 @@ handle_info({'EXIT', _Fd, _Reason}, _State) ->
remove_handler;
handle_info({emulator, _GL, reopen}, State) ->
file:close(State#state.fd),
case file:open(State#state.file, [append]) of
rotate_log(State#state.file),
case file:open(State#state.file, [append, raw]) of
{ok, Fd} ->
{ok, State#state{fd = Fd}};
Error ->
@@ -101,38 +102,38 @@ write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
T = write_time(Time),
case catch io_lib:format(add_node(Format,Pid), Args) of
S when list(S) ->
io:format(Fd, T ++ S, []);
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
F = add_node("ERROR: ~p - ~p~n", Pid),
io:format(Fd, T ++ F, [Format,Args])
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
end;
write_event(Fd, {Time, {emulator, _GL, Chars}}) ->
T = write_time(Time),
case catch io_lib:format(Chars, []) of
S when list(S) ->
io:format(Fd, T ++ S, []);
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
io:format(Fd, T ++ "ERROR: ~p ~n", [Chars])
file:write(Fd, io_lib:format(T ++ "ERROR: ~p ~n", [Chars]))
end;
write_event(Fd, {Time, {info, _GL, {Pid, Info, _}}}) ->
T = write_time(Time),
io:format(Fd, T ++ add_node("~p~n",Pid),[Info]);
file:write(Fd, io_lib:format(T ++ add_node("~p~n",Pid), [Info]));
write_event(Fd, {Time, {error_report, _GL, {Pid, std_error, Rep}}}) ->
T = write_time(Time),
S = format_report(Rep),
io:format(Fd, T ++ S ++ add_node("", Pid), []);
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
T = write_time(Time, "INFO REPORT"),
S = format_report(Rep),
io:format(Fd, T ++ S ++ add_node("", Pid), []);
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
T = write_time(Time, "INFO REPORT"),
case catch io_lib:format(add_node(Format,Pid), Args) of
S when list(S) ->
io:format(Fd, T ++ S, []);
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
F = add_node("ERROR: ~p - ~p~n", Pid),
io:format(Fd, T ++ F, [Format,Args])
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
end;
write_event(_, _) ->
ok.
@@ -189,3 +190,17 @@ write_time({{Y,Mo,D},{H,Mi,S}}, Type) ->
io_lib:format("~n=~s==== ~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ===~n",
[Type, Y, Mo, D, H, Mi, S]).
%% Rename the log file if it the filename exists
%% This is needed in systems when the file must be closed before rotation (Windows).
%% On most Unix-like system, the file can be renamed from the command line and
%%the log can directly be reopened.
rotate_log(Filename) ->
case file:read_file_info(Filename) of
{ok, _FileInfo} ->
RotationName = filename:rootname(Filename),
file:rename(Filename, [RotationName, "-old.log"]),
ok;
{error, _Reason} ->
ok
end.
+240 -76
View File
@@ -10,21 +10,88 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start/3,
receiver/4,
-behaviour(gen_server).
%% API
-export([start_link/5,
start/3,
start/4,
change_shaper/2,
reset_stream/1,
starttls/2]).
starttls/2,
compress/2,
become_controller/1,
close/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-record(state, {socket,
sock_mod,
shaper_state,
c2s_pid,
max_stanza_size,
xml_stream_state,
timeout}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Socket, SockMod, Shaper, MaxStanzaSize, C2SPid) ->
gen_server:start_link(
?MODULE, [Socket, SockMod, Shaper, MaxStanzaSize, C2SPid], []).
%%--------------------------------------------------------------------
%% Function: start() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start(Socket, SockMod, Shaper) ->
proc_lib:spawn(?MODULE, receiver, [Socket, SockMod, Shaper, self()]).
start(Socket, SockMod, Shaper, infinity).
start(Socket, SockMod, Shaper, MaxStanzaSize) ->
{ok, Pid} = supervisor:start_child(
ejabberd_receiver_sup,
[Socket, SockMod, Shaper, MaxStanzaSize, self()]),
Pid.
receiver(Socket, SockMod, Shaper, C2SPid) ->
XMLStreamState = xml_stream:new(C2SPid),
change_shaper(Pid, Shaper) ->
gen_server:cast(Pid, {change_shaper, Shaper}).
reset_stream(Pid) ->
gen_server:call(Pid, reset_stream).
starttls(Pid, TLSSocket) ->
gen_server:call(Pid, {starttls, TLSSocket}).
compress(Pid, ZlibSocket) ->
gen_server:call(Pid, {compress, ZlibSocket}).
become_controller(Pid) ->
gen_server:call(Pid, become_controller).
close(Pid) ->
gen_server:cast(Pid, close).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Socket, SockMod, Shaper, MaxStanzaSize, C2SPid]) ->
XMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
ShaperState = shaper:new(Shaper),
Timeout = case SockMod of
ssl ->
@@ -32,77 +99,174 @@ receiver(Socket, SockMod, Shaper, C2SPid) ->
_ ->
infinity
end,
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamState, Timeout).
{ok, #state{socket = Socket,
sock_mod = SockMod,
shaper_state = ShaperState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize,
xml_stream_state = XMLStreamState,
timeout = Timeout}}.
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamState, Timeout) ->
Res = (catch SockMod:recv(Socket, 0, Timeout)),
receive
{starttls, TLSSocket} ->
xml_stream:close(XMLStreamState),
XMLStreamState1 = xml_stream:new(C2SPid),
TLSRes = case Res of
{ok, Data} ->
tls:recv_data(TLSSocket, Data);
_ ->
tls:recv_data(TLSSocket, "")
end,
receiver1(TLSSocket, tls,
ShaperState, C2SPid, XMLStreamState1, Timeout,
TLSRes);
{change_timeout, NewTimeout} -> % Dirty hack
receiver1(Socket, SockMod,
ShaperState, C2SPid, XMLStreamState, NewTimeout,
Res)
after 0 ->
receiver1(Socket, SockMod,
ShaperState, C2SPid, XMLStreamState, Timeout,
Res)
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({starttls, TLSSocket}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
xml_stream:close(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
NewState = State#state{socket = TLSSocket,
sock_mod = tls,
xml_stream_state = NewXMLStreamState},
case tls:recv_data(TLSSocket, "") of
{ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState)};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call({compress, ZlibSocket}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
xml_stream:close(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
NewState = State#state{socket = ZlibSocket,
sock_mod = ejabberd_zlib,
xml_stream_state = NewXMLStreamState},
case ejabberd_zlib:recv_data(ZlibSocket, "") of
{ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState)};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call(reset_stream, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
xml_stream:close(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
Reply = ok,
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState}};
handle_call(become_controller, _From, State) ->
activate_socket(State),
Reply = ok,
{reply, Reply, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper),
{noreply, State#state{shaper_state = NewShaperState}};
handle_cast(close, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({Tag, _TCPSocket, Data},
#state{socket = Socket,
sock_mod = SockMod} = State)
when (Tag == tcp) or (Tag == ssl) ->
case SockMod of
tls ->
case tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State)};
{error, _Reason} ->
{stop, normal, State}
end;
ejabberd_zlib ->
case ejabberd_zlib:recv_data(Socket, Data) of
{ok, ZlibData} ->
{noreply, process_data(ZlibData, State)};
{error, _Reason} ->
{stop, normal, State}
end;
_ ->
{noreply, process_data(Data, State)}
end;
handle_info({Tag, _TCPSocket}, State)
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
{stop, normal, State};
handle_info({Tag, _TCPSocket, Reason}, State)
when (Tag == tcp_error) or (Tag == ssl_error) ->
case Reason of
timeout ->
{noreply, State};
_ ->
{stop, normal, State}
end;
handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, #state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid} = State) ->
xml_stream:close(XMLStreamState),
gen_fsm:send_event(C2SPid, closed),
catch (State#state.sock_mod):close(State#state.socket),
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
activate_socket(#state{socket = Socket,
sock_mod = SockMod}) ->
case SockMod of
gen_tcp ->
inet:setopts(Socket, [{active, once}]);
_ ->
SockMod:setopts(Socket, [{active, once}])
end.
receiver1(Socket, SockMod, ShaperState, C2SPid, XMLStreamState, Timeout, Res) ->
case Res of
{ok, Text} ->
ShaperSt1 = receive
{change_shaper, Shaper} ->
shaper:new(Shaper)
after 0 ->
ShaperState
end,
NewShaperState = shaper:update(ShaperSt1, size(Text)),
XMLStreamState1 = receive
reset_stream ->
xml_stream:close(XMLStreamState),
xml_stream:new(C2SPid)
after 0 ->
XMLStreamState
end,
XMLStreamState2 = xml_stream:parse(XMLStreamState1, Text),
receiver(Socket, SockMod, NewShaperState, C2SPid, XMLStreamState2,
Timeout);
{error, timeout} ->
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamState,
Timeout);
{error, Reason} ->
xml_stream:close(XMLStreamState),
gen_fsm:send_event(C2SPid, closed),
ok;
{'EXIT', Reason} ->
?ERROR_MSG("(~w) abnormal ~w:recv termination:~n\t~p~n",
[Socket, SockMod, Reason]),
xml_stream:close(XMLStreamState),
gen_fsm:send_event(C2SPid, closed),
ok
end.
change_shaper(Pid, Shaper) ->
Pid ! {change_shaper, Shaper}.
reset_stream(Pid) ->
Pid ! reset_stream.
starttls(Pid, TLSSocket) ->
Pid ! {starttls, TLSSocket}.
process_data(Data,
#state{xml_stream_state = XMLStreamState,
shaper_state = ShaperState} = State) ->
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
if
Pause > 0 ->
erlang:start_timer(Pause, self(), activate);
true ->
activate_socket(State)
end,
State#state{xml_stream_state = XMLStreamState1,
shaper_state = NewShaperState}.
+168 -100
View File
@@ -10,6 +10,9 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
%% API
-export([route/3,
register_route/1,
register_route/2,
@@ -20,114 +23,28 @@
dirty_get_all_domains/0
]).
-export([start_link/0, init/0]).
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(route, {domain, pid, local_hint}).
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
update_tables(),
mnesia:create_table(route,
[{ram_copies, [node()]},
{type, bag},
{attributes,
record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies),
Pid = proc_lib:spawn_link(ejabberd_router, init, []),
register(ejabberd_router, Pid),
{ok, Pid}.
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init() ->
mnesia:subscribe({table, route, simple}),
lists:foreach(
fun(Pid) ->
erlang:monitor(process, Pid)
end,
mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])),
loop().
loop() ->
receive
{route, From, To, Packet} ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
loop();
{mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}} ->
erlang:monitor(process, Pid),
loop();
{'DOWN', _Ref, _Type, Pid, _Info} ->
F = fun() ->
Es = mnesia:select(
route,
[{#route{pid = Pid, _ = '_'},
[],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:transaction(F),
loop();
_ ->
loop()
end.
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
[OrigFrom, OrigTo, OrigPacket]),
LOrigDstDomain = OrigTo#jid.lserver,
case ejabberd_hooks:run_fold(filter_packet, LOrigDstDomain,
{OrigFrom, OrigTo, OrigPacket}, []) of
{From, To, Packet} ->
LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of
[] ->
ejabberd_s2s:route(From, To, Packet);
[R] ->
Pid = R#route.pid,
if
node(Pid) == node() ->
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ ->
Pid ! {route, From, To, Packet}
end;
true ->
Pid ! {route, From, To, Packet}
end;
Rs ->
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(now(), length(Rs)), Rs),
Pid = R#route.pid,
Pid ! {route, From, To, Packet};
LRs ->
LRs,
R = lists:nth(erlang:phash(now(), length(LRs)), LRs),
Pid = R#route.pid,
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ ->
Pid ! {route, From, To, Packet}
end
end
end;
drop ->
ok
end.
%route(From, To, Packet) ->
% ejabberd_router ! {route, From, To, Packet}.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -196,6 +113,157 @@ dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
mnesia:create_table(route,
[{ram_copies, [node()]},
{type, bag},
{attributes,
record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies),
mnesia:subscribe({table, route, simple}),
lists:foreach(
fun(Pid) ->
erlang:monitor(process, Pid)
end,
mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
{noreply, State};
handle_info({mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}},
State) ->
erlang:monitor(process, Pid),
{noreply, State};
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
F = fun() ->
Es = mnesia:select(
route,
[{#route{pid = Pid, _ = '_'},
[],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:transaction(F),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
[OrigFrom, OrigTo, OrigPacket]),
LOrigDstDomain = OrigTo#jid.lserver,
case ejabberd_hooks:run_fold(filter_packet,
{OrigFrom, OrigTo, OrigPacket}, []) of
{From, To, Packet} ->
LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of
[] ->
ejabberd_s2s:route(From, To, Packet);
[R] ->
Pid = R#route.pid,
if
node(Pid) == node() ->
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ ->
Pid ! {route, From, To, Packet}
end;
true ->
Pid ! {route, From, To, Packet}
end;
Rs ->
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(now(), length(Rs)), Rs),
Pid = R#route.pid,
Pid ! {route, From, To, Packet};
LRs ->
LRs,
R = lists:nth(erlang:phash(now(), length(LRs)), LRs),
Pid = R#route.pid,
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ ->
Pid ! {route, From, To, Packet}
end
end
end;
drop ->
ok
end.
update_tables() ->
case catch mnesia:table_info(route, attributes) of
+130 -55
View File
@@ -10,51 +10,39 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start_link/0, init/0,
-behaviour(gen_server).
%% API
-export([start_link/0,
route/3,
have_connection/1,
get_key/1,
try_register/1,
remove_connection/1,
dirty_get_connections/0]).
dirty_get_connections/0,
ctl_process/2
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_ctl.hrl").
-record(s2s, {fromto, pid, key}).
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
Pid = proc_lib:spawn_link(ejabberd_s2s, init, []),
register(ejabberd_s2s, Pid),
{ok, Pid}.
init() ->
update_tables(),
mnesia:create_table(s2s, [{ram_copies, [node()]},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
loop().
loop() ->
receive
{mnesia_system_event, {mnesia_down, Node}} ->
clean_table_from_bad_node(Node),
loop();
{route, From, To, Packet} ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
loop();
_ ->
loop()
end.
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -65,28 +53,12 @@ route(From, To, Packet) ->
ok
end.
remove_connection(FromTo) ->
F = fun() ->
mnesia:delete({s2s, FromTo})
end,
mnesia:transaction(F).
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
s2s,
[{#s2s{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:transaction(F).
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
[_] ->
@@ -123,9 +95,107 @@ try_register(FromTo) ->
false
end.
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
mnesia:create_table(s2s, [{ram_copies, [node()]},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_ctl:register_commands(
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
?MODULE, ctl_process),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
s2s,
[{#s2s{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:transaction(F).
do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
@@ -174,14 +244,19 @@ find_connection(From, To) ->
{atomic, El#s2s.pid}
end.
send_element(Pid, El) ->
Pid ! {send_element, El}.
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
ctl_process(_Val, ["incoming-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
io:format("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["outgoing-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
io:format("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
update_tables() ->
case catch mnesia:table_info(s2s, attributes) of
+30 -12
View File
@@ -1,20 +1,21 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_s2s_in.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Purpose : Serve incoming s2s connection
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_s2s_in).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start/2,
start_link/2,match_domain/2]).
start_link/2,
become_controller/1,
match_domain/2]).
%% gen_fsm callbacks
-export([init/1,
@@ -29,9 +30,8 @@
-include("ejabberd.hrl").
-include("jlib.hrl").
%-include_lib("ssl/pkix/SSL-PKIX.hrl").
-include_lib("ssl/pkix/PKIX1Explicit88.hrl").
-include_lib("ssl/pkix/PKIX1Implicit88.hrl").
-include_lib("ssl/include/PKIX1Explicit88.hrl").
-include_lib("ssl/include/PKIX1Implicit88.hrl").
-include("XmppAddr.hrl").
-define(DICT, dict).
@@ -87,6 +87,9 @@ start(SockData, Opts) ->
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS).
become_controller(Pid) ->
gen_fsm:send_all_state_event(Pid, become_controller).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@@ -100,11 +103,17 @@ start_link(SockData, Opts) ->
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("started: ~p", [{SockMod, Socket}]),
ReceiverPid = ejabberd_receiver:start(Socket, SockMod, none),
Shaper = case lists:keysearch(shaper, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
end,
MaxStanzaSize =
case lists:keysearch(max_stanza_size, 1, Opts) of
{value, {_, Size}} -> Size;
_ -> infinity
end,
ReceiverPid = ejabberd_receiver:start(
Socket, SockMod, none, MaxStanzaSize),
StartTLS = case ejabberd_config:get_local_option(s2s_use_starttls) of
undefined ->
false;
@@ -140,7 +149,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case {xml:get_attr_s("xmlns", Attrs),
xml:get_attr_s("xmlns:db", Attrs),
xml:get_attr_s("version", Attrs) == "1.0"} of
{"jabber:server", "jabber:server:dialback", true} when
{"jabber:server", _, true} when
StateData#state.tls and (not StateData#state.authenticated) ->
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
SASL =
@@ -342,7 +351,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
[]}),
{next_state, stream_established, StateData#state{timer = Timer}};
_ ->
{xmlelement, Name, Attrs, _Els} = El,
NewEl = jlib:remove_attr("xmlns", El),
{xmlelement, Name, Attrs, _Els} = NewEl,
From_s = xml:get_attr_s("from", Attrs),
From = jlib:string_to_jid(From_s),
To_s = xml:get_attr_s("to", Attrs),
@@ -362,7 +372,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
ejabberd_router:route(From, To, El);
ejabberd_router:route(
From, To, NewEl);
true ->
error
end;
@@ -376,7 +387,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
ejabberd_router:route(From, To, El);
ejabberd_router:route(
From, To, NewEl);
true ->
error
end;
@@ -455,6 +467,12 @@ stream_established(closed, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(become_controller, StateName, StateData) ->
ok = (StateData#state.sockmod):controlling_process(
StateData#state.socket,
StateData#state.receiver),
ejabberd_receiver:become_controller(StateData#state.receiver),
{next_state, StateName, StateData};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -499,7 +517,7 @@ handle_info(_, StateName, StateData) ->
%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
(StateData#state.sockmod):close(StateData#state.socket),
ejabberd_receiver:close(StateData#state.receiver),
ok.
%%%----------------------------------------------------------------------
+115 -54
View File
@@ -8,7 +8,6 @@
-module(ejabberd_s2s_out).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
@@ -25,12 +24,14 @@
wait_for_features/2,
wait_for_auth_result/2,
wait_for_starttls_proceed/2,
reopen_socket/2,
stream_established/2,
handle_event/3,
handle_sync_event/4,
handle_info/3,
terminate/3,
code_change/4]).
code_change/4,
test_get_addr_port/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -44,6 +45,7 @@
tls_enabled = false,
tls_options = [],
authenticated = false,
db_enabled = true,
try_auth = true,
myname, server, queue,
new = false, verify = false,
@@ -140,7 +142,44 @@ init([From, Server, Type]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
open_socket(init, StateData) ->
{Addr, Port} = get_addr_port(StateData#state.server),
AddrList = get_addr_port(StateData#state.server),
case lists:foldl(fun({Addr, Port}, Acc) ->
case Acc of
{ok, Socket} ->
{ok, Socket};
_ ->
open_socket1(Addr, Port)
end
end, {error, badarg}, AddrList) of
{ok, Socket} ->
ReceiverPid = ejabberd_receiver:start(Socket, gen_tcp, none),
ok = gen_tcp:controlling_process(Socket, ReceiverPid),
ejabberd_receiver:become_controller(ReceiverPid),
Version = if
StateData#state.use_v10 ->
" version='1.0'";
true ->
""
end,
NewStateData = StateData#state{socket = Socket,
sockmod = gen_tcp,
tls_enabled = false,
receiver = ReceiverPid,
streamid = new_id()},
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
[StateData#state.server,
Version])),
{next_state, wait_for_stream, NewStateData};
{error, _Reason} ->
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
bounce_messages(Error),
{stop, normal, StateData}
end;
open_socket(_, StateData) ->
{next_state, open_socket, StateData}.
%%----------------------------------------------------------------------
open_socket1(Addr, Port) ->
Res = case idna:domain_utf8_to_ascii(Addr) of
false -> {error, badarg};
ASCIIAddr ->
@@ -163,35 +202,16 @@ open_socket(init, StateData) ->
end,
case Res of
{ok, Socket} ->
ReceiverPid = ejabberd_receiver:start(Socket, gen_tcp, none),
Version = if
StateData#state.use_v10 ->
" version='1.0'";
true ->
""
end,
NewStateData = StateData#state{socket = Socket,
sockmod = gen_tcp,
tls_enabled = false,
receiver = ReceiverPid,
streamid = new_id()},
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
[StateData#state.server,
Version])),
{next_state, wait_for_stream, NewStateData};
{ok, Socket};
{error, Reason} ->
?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
bounce_messages(Error),
{stop, normal, StateData};
{error, Reason};
{'EXIT', Reason} ->
?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
bounce_messages(Error),
{stop, normal, StateData}
end;
open_socket(_, StateData) ->
{next_state, open_socket, StateData}.
{error, Reason}
end.
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
@@ -204,13 +224,7 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
StateData#state.use_v10 ->
{next_state, wait_for_features, StateData};
{"jabber:server", "", true} when StateData#state.use_v10 ->
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
% TODO: clear message queue
(StateData#state.sockmod):close(StateData#state.socket),
gen_fsm:send_event(self(), init),
{next_state, open_socket, StateData#state{socket = undefined,
use_v10 = false}};
{next_state, wait_for_features, StateData#state{db_enabled = false}};
_ ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
{stop, normal, StateData}
@@ -342,7 +356,6 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
StateData#state{try_auth = false}};
StartTLS and StateData#state.tls and
(not StateData#state.tls_enabled) ->
StateData#state.receiver ! {change_timeout, 100},
send_element(StateData,
{xmlelement, "starttls",
[{"xmlns", ?NS_TLS}], []}),
@@ -350,13 +363,19 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
StartTLSRequired and (not StateData#state.tls) ->
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
(StateData#state.sockmod):close(StateData#state.socket),
gen_fsm:send_event(self(), init),
{next_state, open_socket,
ejabberd_receiver:close(StateData#state.receiver),
{next_state, reopen_socket,
StateData#state{socket = undefined,
use_v10 = false}};
StateData#state.db_enabled ->
send_db_request(StateData);
true ->
send_db_request(StateData)
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
% TODO: clear message queue
ejabberd_receiver:close(StateData#state.receiver),
{next_state, reopen_socket, StateData#state{socket = undefined,
use_v10 = false}}
end;
_ ->
send_text(StateData,
@@ -408,9 +427,8 @@ wait_for_auth_result({xmlstreamelement, El}, StateData) ->
?NS_SASL ->
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
(StateData#state.sockmod):close(StateData#state.socket),
gen_fsm:send_event(self(), init),
{next_state, open_socket,
ejabberd_receiver:close(StateData#state.receiver),
{next_state, reopen_socket,
StateData#state{socket = undefined}};
_ ->
send_text(StateData,
@@ -462,7 +480,6 @@ wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(
StateData#state.receiver, TLSSocket),
StateData#state.receiver ! {change_timeout, infinity},
NewStateData = StateData#state{sockmod = tls,
socket = TLSSocket,
streamid = new_id(),
@@ -498,6 +515,19 @@ wait_for_starttls_proceed(closed, StateData) ->
{stop, normal, StateData}.
reopen_socket({xmlstreamelement, El}, StateData) ->
{next_state, reopen_socket, StateData};
reopen_socket({xmlstreamend, Name}, StateData) ->
{next_state, reopen_socket, StateData};
reopen_socket({xmlstreamerror, _}, StateData) ->
{next_state, reopen_socket, StateData};
reopen_socket(timeout, StateData) ->
{stop, normal, StateData};
reopen_socket(closed, StateData) ->
gen_fsm:send_event(self(), init),
{next_state, open_socket, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
?INFO_MSG("stream established", []),
case is_verify_res(El) of
@@ -630,8 +660,7 @@ handle_info(_, StateName, StateData) ->
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
bounce_queue(StateData#state.queue, Error),
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
case StateData#state.new of
false ->
ok;
@@ -642,8 +671,8 @@ terminate(Reason, StateName, StateData) ->
case StateData#state.socket of
undefined ->
ok;
Socket ->
(StateData#state.sockmod):close(Socket)
_Socket ->
ejabberd_receiver:close(StateData#state.receiver)
end,
ok.
@@ -779,18 +808,50 @@ get_addr_port(Server) ->
case Res of
{error, Reason} ->
?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
{Server, ejabberd_config:get_local_option(outgoing_s2s_port)};
[{Server, ejabberd_config:get_local_option(outgoing_s2s_port)}];
{ok, HEnt} ->
?DEBUG("srv lookup of '~s': ~p~n",
[Server, HEnt#hostent.h_addr_list]),
case HEnt#hostent.h_addr_list of
[] ->
{Server,
ejabberd_config:get_local_option(outgoing_s2s_port)};
[{_, _, Port, Host} | _] ->
{Host, Port}
[{Server,
ejabberd_config:get_local_option(outgoing_s2s_port)}];
AddrList ->
% Probabilities are not exactly proportional to weights
% for simplicity (higher weigths are overvalued)
{A1, A2, A3} = now(),
random:seed(A1, A2, A3),
case (catch lists:map(
fun({Priority, Weight, Port, Host}) ->
N = case Weight of
0 -> 0;
_ -> (Weight + 1) * random:uniform()
end,
{Priority * 65536 - N, Host, Port}
end, AddrList)) of
{'EXIT', _Reasn} ->
[{Server,
ejabberd_config:get_local_option(outgoing_s2s_port)}];
SortedList ->
List = lists:map(
fun({_, Host, Port}) ->
{Host, Port}
end, lists:keysort(1, SortedList)),
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
List
end
end
end.
test_get_addr_port(Server) ->
lists:foldl(
fun(_, Acc) ->
[HostPort | _] = get_addr_port(Server),
case lists:keysearch(HostPort, 1, Acc) of
false ->
[{HostPort, 1} | Acc];
{value, {_, Num}} ->
lists:keyreplace(HostPort, 1, Acc, {HostPort, Num + 1})
end
end, [], lists:seq(1, 100000)).
+22 -23
View File
@@ -13,7 +13,11 @@
-behaviour(gen_fsm).
%% External exports
-export([start/2, start_link/2, receiver/3, send_text/2, send_element/2]).
-export([start/2,
start_link/2,
send_text/2,
send_element/2,
become_controller/1]).
%% gen_fsm callbacks
-export([init/1,
@@ -75,6 +79,9 @@ start(SockData, Opts) ->
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_service, [SockData, Opts], ?FSMOPTS).
become_controller(Pid) ->
gen_fsm:send_all_state_event(Pid, become_controller).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@@ -116,7 +123,7 @@ init([{SockMod, Socket}, Opts]) ->
false
end
end,
ReceiverPid = spawn(?MODULE, receiver, [Socket, SockMod, self()]),
ReceiverPid = ejabberd_receiver:start(Socket, SockMod, none),
{ok, wait_for_stream, #state{socket = Socket,
receiver = ReceiverPid,
streamid = new_id(),
@@ -171,11 +178,11 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
end, StateData#state.hosts),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_HEADER_ERR),
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData}
end;
_ ->
{next_state, wait_for_key, StateData}
{next_state, wait_for_handshake, StateData}
end;
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
@@ -190,7 +197,8 @@ wait_for_handshake(closed, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, Attrs, _Els} = El,
NewEl = jlib:remove_attr("xmlns", El),
{xmlelement, Name, Attrs, _Els} = NewEl,
From = xml:get_attr_s("from", Attrs),
FromJID1 = jlib:string_to_jid(From),
FromJID = case FromJID1 of
@@ -210,9 +218,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
(Name == "message") or
(Name == "presence")) and
(ToJID /= error) and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, El);
ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
send_element(StateData, Err),
error
end,
@@ -251,6 +259,12 @@ stream_established(closed, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(become_controller, StateName, StateData) ->
ok = (StateData#state.sockmod):controlling_process(
StateData#state.socket,
StateData#state.receiver),
ejabberd_receiver:become_controller(StateData#state.receiver),
{next_state, StateName, StateData};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -314,28 +328,13 @@ terminate(Reason, StateName, StateData) ->
_ ->
ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
ejabberd_receiver:close(StateData#state.receiver),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
receiver(Socket, SockMod, C2SPid) ->
XMLStreamPid = xml_stream:start(C2SPid),
receiver(Socket, SockMod, C2SPid, XMLStreamPid).
receiver(Socket, SockMod, C2SPid, XMLStreamPid) ->
case SockMod:recv(Socket, 0) of
{ok, Text} ->
xml_stream:send_text(XMLStreamPid, Text),
receiver(Socket, SockMod, C2SPid, XMLStreamPid);
{error, _Reason} ->
exit(XMLStreamPid, closed),
gen_fsm:send_event(C2SPid, closed),
ok
end.
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,Text).
+373 -224
View File
@@ -8,89 +8,51 @@
-module(ejabberd_sm).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start_link/0, init/0,
-behaviour(gen_server).
%% API
-export([start_link/0,
route/3,
open_session/3, close_session/3,
open_session/4, close_session/4,
bounce_offline_message/3,
disconnect_removed_user/2,
get_user_resources/2,
set_presence/4,
unset_presence/4,
set_presence/5,
unset_presence/5,
close_session_unset_presence/5,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2
unregister_iq_handler/2,
ctl_process/2
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_ctl.hrl").
-record(session, {usr, us, pid}).
-record(presence, {usr, us, priority}).
-record(session, {sid, usr, us, priority}).
-record(state, {}).
%% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
Pid = proc_lib:spawn_link(ejabberd_sm, init, []),
register(ejabberd_sm, Pid),
{ok, Pid}.
init() ->
update_tables(),
mnesia:create_table(session, [{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
mnesia:add_table_index(session, us),
mnesia:add_table_copy(session, node(), ram_copies),
mnesia:create_table(presence,
[{ram_copies, [node()]},
{attributes, record_info(fields, presence)}]),
mnesia:add_table_index(presence, us),
mnesia:subscribe(system),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(offline_message_hook, Host,
ejabberd_sm, bounce_offline_message, 100),
ejabberd_hooks:add(remove_user, Host,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
loop().
loop() ->
receive
{route, From, To, Packet} ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
loop();
{mnesia_system_event, {mnesia_down, Node}} ->
clean_table_from_bad_node(Node),
loop();
{register_iq_handler, Host, XMLNS, Module, Function} ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
loop();
{register_iq_handler, Host, XMLNS, Module, Function, Opts} ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}),
loop();
{unregister_iq_handler, Host, XMLNS} ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ ->
ok
end,
ets:delete(sm_iqtable, {XMLNS, Host}),
loop();
_ ->
loop()
end.
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -101,59 +63,231 @@ route(From, To, Packet) ->
ok
end.
open_session(User, Server, Resource) ->
register_connection(User, Server, Resource, self()).
open_session(SID, User, Server, Resource) ->
set_session(SID, User, Server, Resource, undefined),
check_for_sessions_to_replace(User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
[SID, JID]).
close_session(User, Server, Resource) ->
remove_connection(User, Server, Resource).
close_session(SID, User, Server, Resource) ->
F = fun() ->
mnesia:delete({session, SID})
end,
mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
[SID, JID]).
bounce_offline_message(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err),
stop.
disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jlib:make_jid("", "", ""),
jlib:make_jid(User, Server, ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}).
get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_index_read(session, US, #session.us) of
{'EXIT', _Reason} ->
[];
Ss ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
end.
set_presence(SID, User, Server, Resource, Priority) ->
set_session(SID, User, Server, Resource, Priority).
unset_presence(SID, User, Server, Resource, Status) ->
set_session(SID, User, Server, Resource, undefined),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]).
close_session_unset_presence(SID, User, Server, Resource, Status) ->
close_session(SID, User, Server, Resource),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]).
register_connection(User, Server, Resource, Pid) ->
dirty_get_sessions_list() ->
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
[],
['$1']}]).
dirty_get_my_sessions_list() ->
mnesia:dirty_select(
session,
[{#session{sid = {'_', '$1'}, _ = '_'},
[{'==', {node, '$1'}, node()}],
['$_']}]).
get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
mnesia:create_table(session,
[{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
mnesia:add_table_index(session, usr),
mnesia:add_table_index(session, us),
mnesia:add_table_copy(session, node(), ram_copies),
mnesia:subscribe(system),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(offline_message_hook, Host,
ejabberd_sm, bounce_offline_message, 100),
ejabberd_hooks:add(remove_user, Host,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
ejabberd_ctl:register_commands(
[{"connected-users", "list all established sessions"},
{"connected-users-number", "print a number of established sessions"},
{"user-resources user server", "print user's connected resources"}],
?MODULE, ctl_process),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
{noreply, State};
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}),
{noreply, State};
handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ ->
ok
end,
ets:delete(sm_iqtable, {XMLNS, Host}),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
set_session(SID, User, Server, Resource, Priority) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
F = fun() ->
Ss = mnesia:wread({session, USR}),
mnesia:write(#session{usr = USR, us = US, pid = Pid}),
Ss
end,
case mnesia:transaction(F) of
{atomic, Ss} ->
lists:foreach(
fun(R) ->
R#session.pid ! replaced
end, Ss);
_ ->
false
end.
remove_connection(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LResource = jlib:resourceprep(Resource),
LServer = jlib:nameprep(Server),
USR = {LUser, LServer, LResource},
F = fun() ->
mnesia:delete({session, USR})
end,
mnesia:transaction(F).
mnesia:write(#session{sid = SID,
usr = USR,
us = US,
priority = Priority})
end,
mnesia:sync_dirty(F).
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
session,
[{#session{pid = '$1', _ = '_'},
[{#session{sid = {'_', '$1'}, _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E),
mnesia:delete({presence, E#session.usr})
mnesia:delete({session, E#session.sid})
end, Es)
end,
mnesia:transaction(F).
mnesia:sync_dirty(F).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -171,32 +305,35 @@ do_route(From, To, Packet) ->
{Pass, Subsc} =
case xml:get_attr_s("type", Attrs) of
"subscribe" ->
Reason = xml:get_path_s(
Packet,
[{elem, "status"}, cdata]),
{ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, subscribe]),
[User, Server, From, subscribe, Reason]),
true};
"subscribed" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, subscribed]),
[User, Server, From, subscribed, ""]),
true};
"unsubscribe" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, unsubscribe]),
[User, Server, From, unsubscribe, ""]),
true};
"unsubscribed" ->
{ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, unsubscribed]),
[User, Server, From, unsubscribed, ""]),
true};
_ ->
{true, false}
@@ -205,39 +342,18 @@ do_route(From, To, Packet) ->
LFrom = jlib:jid_tolower(From),
PResources = get_user_present_resources(
LUser, LServer),
if
PResources /= [] ->
lists:foreach(
fun({_, R}) ->
if LFrom /=
{LUser, LServer, R} ->
do_route(
From,
jlib:jid_replace_resource(To, R),
Packet);
true ->
ok
end
end, PResources);
true ->
if
Subsc ->
case ejabberd_auth:is_user_exists(
LUser, LServer) of
true ->
ejabberd_hooks:run(
offline_subscription_hook,
LServer,
[From, To, Packet]);
_ ->
Err = jlib:make_error_reply(
Packet, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end;
true ->
ok
end
end;
lists:foreach(
fun({_, R}) ->
if LFrom /=
{LUser, LServer, R} ->
do_route(
From,
jlib:jid_replace_resource(To, R),
Packet);
true ->
ok
end
end, PResources);
true ->
ok
end;
@@ -257,7 +373,7 @@ do_route(From, To, Packet) ->
end;
_ ->
USR = {LUser, LServer, LResource},
case mnesia:dirty_read({session, USR}) of
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
case Name of
"message" ->
@@ -275,8 +391,9 @@ do_route(From, To, Packet) ->
_ ->
?DEBUG("packet droped~n", [])
end;
[Sess] ->
Pid = Sess#session.pid,
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end
@@ -285,19 +402,29 @@ do_route(From, To, Packet) ->
route_message(From, To, Packet) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
case catch lists:max(get_user_present_resources(LUser, LServer)) of
{Priority, R} when is_integer(Priority),
Priority >= 0 ->
LResource = jlib:resourceprep(R),
USR = {LUser, LServer, LResource},
case mnesia:dirty_read({session, USR}) of
[] ->
ok; % Race condition
[Sess] ->
Pid = Sess#session.pid,
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end;
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
{Priority, _R} when is_integer(Priority), Priority >= 0 ->
lists:foreach(
%% Route messages to all priority that equals the max, if
%% positive
fun({P, R}) when P == Priority ->
LResource = jlib:resourceprep(R),
USR = {LUser, LServer, LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
ok; % Race condition
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end;
%% Ignore other priority:
({_Prio, _Res}) ->
ok
end,
PrioRes);
_ ->
case xml:get_tag_attr_s("type", Packet) of
"error" ->
@@ -316,84 +443,94 @@ route_message(From, To, Packet) ->
end
end.
bounce_offline_message(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err),
stop.
disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jlib:make_jid("", "", ""),
jlib:make_jid(User, Server, ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
clean_session_list([], Res) ->
Res;
clean_session_list([S], Res) ->
[S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
if
S1#session.usr == S2#session.usr ->
if
S1#session.sid > S2#session.sid ->
clean_session_list([S1 | Rest], Res);
true ->
clean_session_list([S2 | Rest], Res)
end;
true ->
clean_session_list([S2 | Rest], [S1 | Res])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
get_user_present_resources(LUser, LServer) ->
US = {LUser, LServer},
case catch mnesia:dirty_index_read(session, US, #session.us) of
{'EXIT', _Reason} ->
[];
Rs ->
lists:map(fun(R) ->
element(3, R#session.usr)
end, Rs)
Ss ->
[{S#session.priority, element(3, S#session.usr)} ||
S <- clean_session_list(Ss), is_integer(S#session.priority)]
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_presence(User, Server, Resource, Priority) ->
%% On new session, check if some existing connections need to be replace
check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
USR = {User, Server, Resource},
US = {LUser, LServer},
F = fun() ->
mnesia:write(#presence{usr = USR, us = US,
priority = Priority})
end,
mnesia:transaction(F).
LResource = jlib:resourceprep(Resource),
unset_presence(User, Server, Resource, Status) ->
USR = {User, Server, Resource},
F = fun() ->
mnesia:delete({presence, USR})
end,
mnesia:transaction(F),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]).
%% TODO: Depending on how this is executed, there could be an unneeded
%% replacement for max_sessions. We need to check this at some point.
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
get_user_present_resources(LUser, LServer) ->
US = {LUser, LServer},
case catch mnesia:dirty_index_read(presence, US, #presence.us) of
{'EXIT', _Reason} ->
[];
Rs ->
lists:map(fun(R) ->
{R#presence.priority, element(3, R#presence.usr)}
end, Rs)
check_existing_resources(LUser, LServer, LResource) ->
USR = {LUser, LServer, LResource},
%% A connection exist with the same resource. We replace it:
SIDs = mnesia:dirty_select(
session,
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]),
if
SIDs == [] -> ok;
true ->
MaxSID = lists:max(SIDs),
lists:foreach(
fun({_, Pid} = S) when S /= MaxSID ->
Pid ! replaced;
(_) -> ok
end, SIDs)
end.
dirty_get_sessions_list() ->
mnesia:dirty_all_keys(session).
check_max_sessions(LUser, LServer) ->
%% If the max number of sessions for a given is reached, we replace the
%% first one
SIDs = mnesia:dirty_select(
session,
[{#session{sid = '$1', usr = {LUser, LServer, '_'}, _ = '_'}, [], ['$1']}]),
MaxSessions = get_max_user_sessions(LServer),
if length(SIDs) =< MaxSessions -> ok;
true -> {_, Pid} = lists:min(SIDs),
Pid ! replaced
end.
dirty_get_my_sessions_list() ->
mnesia:dirty_select(
session,
[{#session{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, node()}],
['$_']}]).
get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
%% Get the user_max_session setting
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
get_max_user_sessions(Host) ->
case ejabberd_config:get_local_option({max_user_sessions, Host}) of
undefined -> ?MAX_USER_SESSIONS;
Max -> Max
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -418,7 +555,7 @@ process_iq(From, To, Packet) ->
From, To, IQ);
[] ->
Err = jlib:make_error_reply(
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
Packet, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end;
reply ->
@@ -429,15 +566,27 @@ process_iq(From, To, Packet) ->
ok
end.
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
ctl_process(_Val, ["connected-users"]) ->
USRs = dirty_get_sessions_list(),
NewLine = io_lib:format("~n", []),
SUSRs = lists:sort(USRs),
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
io:format("~s", [FUSRs]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["connected-users-number"]) ->
N = length(dirty_get_sessions_list()),
io:format("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["user-resources", User, Server]) ->
Resources = get_user_resources(User, Server),
NewLine = io_lib:format("~n", []),
SResources = lists:sort(Resources),
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
io:format("~s", [FResources]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
update_tables() ->
@@ -447,16 +596,16 @@ update_tables() ->
[ur, user, pid] ->
mnesia:delete_table(session);
[usr, us, pid] ->
mnesia:delete_table(session);
[sid, usr, us, priority] ->
ok;
{'EXIT', _} ->
ok
end,
case catch mnesia:table_info(presence, attributes) of
[ur, user, priority] ->
case lists:member(presence, mnesia:system_info(tables)) of
true ->
mnesia:delete_table(presence);
[usr, us, priority] ->
ok;
{'EXIT', _} ->
false ->
ok
end,
case lists:member(local_session, mnesia:system_info(tables)) of
+9
View File
@@ -68,6 +68,14 @@ init([]) ->
infinity,
supervisor,
[ejabberd_listener]},
ReceiverSupervisor =
{ejabberd_receiver_sup,
{ejabberd_tmp_sup, start_link,
[ejabberd_receiver_sup, ejabberd_receiver]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
C2SSupervisor =
{ejabberd_c2s_sup,
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
@@ -130,6 +138,7 @@ init([]) ->
SM,
S2S,
Local,
ReceiverSupervisor,
C2SSupervisor,
S2SInSupervisor,
S2SOutSupervisor,
+128
View File
@@ -0,0 +1,128 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_update.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Update ejabberd
%%% Created : 27 Jan 2006 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id: ejabberd_c2s.erl 492 2006-01-25 00:35:12Z alexey $
%%%-------------------------------------------------------------------
-module(ejabberd_update).
-author('alexey@sevcom.net').
%% API
-export([update/0, update_info/0]).
%%====================================================================
%% API
%%====================================================================
update() ->
case update_info() of
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
Eval =
release_handler_1:eval_script(
LowLevelScript, [],
[{ejabberd, "", filename:join(Dir, "..")}]),
io:format("eval: ~p~n", [Eval]),
Eval;
{error, Reason} ->
{error, Reason}
end.
update_info() ->
Dir = filename:dirname(code:which(ejabberd)),
case file:list_dir(Dir) of
{ok, Files} ->
Beams = [list_to_atom(filename:rootname(FN)) ||
FN <- Files, lists:suffix(".beam", FN)],
UpdatedBeams =
lists:filter(
fun(Module) ->
{ok, {Module, [NewVsn]}} =
beam_lib:version(code:which(Module)),
case code:is_loaded(Module) of
{file, _} ->
Attrs = Module:module_info(attributes),
{value, {vsn, [CurVsn]}} =
lists:keysearch(vsn, 1, Attrs),
NewVsn /= CurVsn;
false ->
false
end
end, Beams),
io:format("beam files: ~p~n", [UpdatedBeams]),
Script = make_script(UpdatedBeams),
io:format("script: ~p~n", [Script]),
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
io:format("low level script: ~p~n", [LowLevelScript]),
Check =
release_handler_1:check_script(
LowLevelScript,
[{ejabberd, "", filename:join(Dir, "..")}]),
io:format("check: ~p~n", [Check]),
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check};
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%% From systools.hrl
-record(application,
{name, %% Name of the application, atom().
type = permanent, %% Application start type, atom().
vsn = "", %% Version of the application, string().
id = "", %% Id of the application, string().
description = "", %% Description of application, string().
modules = [], %% [Module | {Module,Vsn}] of modules
%% incorporated in the application,
%% Module = atom(), Vsn = string().
uses = [], %% [Application] list of applications required
%% by the application, Application = atom().
includes = [], %% [Application] list of applications included
%% by the application, Application = atom().
regs = [], %% [RegNames] a list of registered process
%% names used by the application, RegNames =
%% atom().
env = [], %% [{Key,Value}] environment variable of
%% application, Key = Value = term().
maxT = infinity, %% Max time an application may exist,
%% integer() | infinity.
maxP = infinity, %% Max number of processes in an application,
%% integer() | infinity.
mod = [], %% [] | {Mod, StartArgs}, Mod= atom(),
%% StartArgs = list().
start_phases = [], %% [] | {Phase, PhaseArgs}, Phase = atom(),
%% PhaseArgs = list().
dir = "" %% The directory where the .app file was
%% found (internal use).
}).
make_script(UpdatedBeams) ->
lists:map(
fun(Module) ->
{ok, {Module, [{attributes, NewAttrs}]}} =
beam_lib:chunks(code:which(Module), [attributes]),
CurAttrs = Module:module_info(attributes),
case lists:keysearch(update_info, 1, NewAttrs) of
{value, {_, [{update, _}]}} ->
case lists:keysearch(update_info, 1, CurAttrs) of
{value, {_, [{update, Extra}]}} ->
{update, Module, {advanced, Extra}};
false ->
{update, Module, {advanced, 0}}
end;
false ->
{load_module, Module}
end
end, UpdatedBeams).
make_low_level_script(UpdatedBeams, Script) ->
EJDApp = #application{name = ejabberd,
modules = UpdatedBeams},
{ok, LowLevelScript} =
systools_rc:translate_scripts([Script], [EJDApp], [EJDApp]),
LowLevelScript.
+45
View File
@@ -0,0 +1,45 @@
# $Id$
CC = @CC@
CFLAGS = @CFLAGS@ @ZLIB_CFLAGS@ @ERLANG_CFLAGS@
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ @ZLIB_LIBS@ @ERLANG_LIBS@
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
else
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
endif
SUBDIRS =
ERLSHLIBS = ../ejabberd_zlib_drv.so
OUTDIR = ..
EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)/ejabberd_zlib.beam
all: $(OBJS) $(ERLSHLIBS)
$(OUTDIR)/%.beam: %.erl
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
#all: $(ERLSHLIBS)
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
$(ERLSHLIBS): ../%.so: %.c
$(CC) -Wall $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
$(subst ../,,$(subst .so,.c,$@)) $(LIBS) \
-o $@ $(DYNAMIC_LIB_CFLAGS)
clean:
rm -f $(OBJS) $(ERLSHLIBS)
distclean: clean
rm -f Makefile
TAGS:
etags *.erl
+37
View File
@@ -0,0 +1,37 @@
include ..\Makefile.inc
OUTDIR = ..
EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)\ejabberd_zlib.beam
SOURCE = ejabberd_zlib_drv.c
OBJECT = ejabberd_zlib_drv.o
DLL = $(OUTDIR)\ejabberd_zlib_drv.dll
ALL : $(DLL) $(OBJS)
CLEAN :
-@erase $(DLL)
-@erase $(OUTDIR)\ejabberd_zlib_drv.exp
-@erase $(OUTDIR)\ejabberd_zlib_drv.lib
-@erase $(OBJECT)
-@erase $(OBJS)
$(OUTDIR)\ejabberd_zlib.beam : ejabberd_zlib.erl
erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_zlib.erl
CC=cl.exe
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include" -I"$(ZLIB_DIR)\include"
LD=link.exe
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(ZLIB_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
$(DLL) : $(OBJECT)
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
$(OBJECT) : $(SOURCE)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
+134
View File
@@ -0,0 +1,134 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_zlib.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Interface to zlib
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_zlib).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
-export([start/0, start_link/0,
enable_zlib/2, disable_zlib/1,
send/2,
recv/2, recv/3, recv_data/2,
setopts/2,
controlling_process/2,
close/1]).
%% Internal exports, call-back functions.
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
-define(DEFLATE, 1).
-define(INFLATE, 2).
-record(zlibsock, {sockmod, socket, zlibport}).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
ok -> ok;
{error, already_loaded} -> ok
end,
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
{ok, Port}.
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
handle_call(_, _, State) ->
{noreply, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
handle_info(_, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, Port) ->
Port ! {self, close},
ok.
enable_zlib(SockMod, Socket) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
ok -> ok;
{error, already_loaded} -> ok
end,
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
port_close(Port),
{SockMod, Socket}.
recv(Socket, Length) ->
recv(Socket, Length, infinity).
recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
Length, Timeout) ->
case SockMod:recv(Socket, Length, Timeout) of
{ok, Packet} ->
recv_data(ZlibSock, Packet);
{error, _Reason} = Error ->
Error
end.
recv_data(#zlibsock{zlibport = Port} = _ZlibSock, Packet) ->
case port_control(Port, ?INFLATE, Packet) of
<<0, In/binary>> ->
{ok, In};
<<1, Error/binary>> ->
{error, binary_to_list(Error)}
end.
send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port},
Packet) ->
case port_control(Port, ?DEFLATE, Packet) of
<<0, Out/binary>> ->
SockMod:send(Socket, Out);
<<1, Error/binary>> ->
{error, binary_to_list(Error)}
end.
setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) ->
case SockMod of
gen_tcp ->
inet:setopts(Socket, Opts);
_ ->
SockMod:setopts(Socket, Opts)
end.
controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) ->
SockMod:controlling_process(Socket, Pid).
close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
SockMod:close(Socket),
port_close(Port).
+171
View File
@@ -0,0 +1,171 @@
/* $Id$ */
#include <stdio.h>
#include <string.h>
#include <erl_driver.h>
#include <zlib.h>
#define BUF_SIZE 1024
typedef struct {
ErlDrvPort port;
z_stream *d_stream;
z_stream *i_stream;
} ejabberd_zlib_data;
static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff)
{
ejabberd_zlib_data *d =
(ejabberd_zlib_data *)driver_alloc(sizeof(ejabberd_zlib_data));
d->port = port;
d->d_stream = (z_stream *)malloc(sizeof(z_stream));
d->d_stream->zalloc = (alloc_func)0;
d->d_stream->zfree = (free_func)0;
d->d_stream->opaque = (voidpf)0;
deflateInit(d->d_stream, Z_DEFAULT_COMPRESSION);
d->i_stream = (z_stream *)malloc(sizeof(z_stream));
d->i_stream->zalloc = (alloc_func)0;
d->i_stream->zfree = (free_func)0;
d->i_stream->opaque = (voidpf)0;
inflateInit(d->i_stream);
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)d;
}
static void ejabberd_zlib_drv_stop(ErlDrvData handle)
{
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
deflateEnd(d->d_stream);
free(d->d_stream);
inflateEnd(d->i_stream);
free(d->i_stream);
driver_free((char *)handle);
}
#define DEFLATE 1
#define INFLATE 2
#define die_unless(cond, errstr) \
if (!(cond)) \
{ \
rlen = strlen(errstr) + 1; \
b = driver_realloc_binary(b, rlen); \
b->orig_bytes[0] = 1; \
strncpy(b->orig_bytes + 1, errstr, rlen - 1); \
*rbuf = (char *)b; \
return rlen; \
}
static int ejabberd_zlib_drv_control(ErlDrvData handle,
unsigned int command,
char *buf, int len,
char **rbuf, int rlen)
{
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
int err;
int size;
ErlDrvBinary *b;
switch (command)
{
case DEFLATE:
size = BUF_SIZE + 1;
rlen = 1;
b = driver_alloc_binary(size);
b->orig_bytes[0] = 0;
d->d_stream->next_in = buf;
d->d_stream->avail_in = len;
d->d_stream->avail_out = 0;
err = Z_OK;
while (err == Z_OK && d->d_stream->avail_out == 0)
{
d->d_stream->next_out = b->orig_bytes + rlen;
d->d_stream->avail_out = BUF_SIZE;
err = deflate(d->d_stream, Z_SYNC_FLUSH);
die_unless((err == Z_OK) || (err == Z_STREAM_END),
"Deflate error");
rlen += (BUF_SIZE - d->d_stream->avail_out);
size += (BUF_SIZE - d->d_stream->avail_out);
b = driver_realloc_binary(b, size);
}
b = driver_realloc_binary(b, rlen);
*rbuf = (char *)b;
return rlen;
case INFLATE:
size = BUF_SIZE + 1;
rlen = 1;
b = driver_alloc_binary(size);
b->orig_bytes[0] = 0;
if (len > 0) {
d->i_stream->next_in = buf;
d->i_stream->avail_in = len;
d->i_stream->avail_out = 0;
err = Z_OK;
while (err == Z_OK && d->i_stream->avail_out == 0)
{
d->i_stream->next_out = b->orig_bytes + rlen;
d->i_stream->avail_out = BUF_SIZE;
err = inflate(d->i_stream, Z_SYNC_FLUSH);
die_unless((err == Z_OK) || (err == Z_STREAM_END),
"Inflate error");
rlen += (BUF_SIZE - d->i_stream->avail_out);
size += (BUF_SIZE - d->i_stream->avail_out);
b = driver_realloc_binary(b, size);
}
}
b = driver_realloc_binary(b, rlen);
*rbuf = (char *)b;
return rlen;
}
b = driver_alloc_binary(1);
b->orig_bytes[0] = 0;
*rbuf = (char *)b;
return 1;
}
ErlDrvEntry ejabberd_zlib_driver_entry = {
NULL, /* F_PTR init, N/A */
ejabberd_zlib_drv_start, /* L_PTR start, called when port is opened */
ejabberd_zlib_drv_stop, /* F_PTR stop, called when port is closed */
NULL, /* F_PTR output, called when erlang has sent */
NULL, /* F_PTR ready_input, called when input descriptor ready */
NULL, /* F_PTR ready_output, called when output descriptor ready */
"ejabberd_zlib_drv", /* char *driver_name, the argument to open_port */
NULL, /* F_PTR finish, called when unloaded */
NULL, /* handle */
ejabberd_zlib_drv_control, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL /* F_PTR outputv, reserved */
};
DRIVER_INIT(ejabberd_zlib_drv) /* must match name in driver_entry */
{
return &ejabberd_zlib_driver_entry;
}
+4 -2
View File
@@ -74,7 +74,7 @@ export_roster(Server, Output) ->
" and jid='", SJID, "';"
"insert into rosterusers("
" username, jid, nick, "
" subscription, ask, "
" subscription, ask, askmessage, "
" server, subscribe, type) "
" values ", ItemVals, ";"
"delete from rostergroups "
@@ -271,7 +271,8 @@ output(LServer, IO, SQL) ->
record_to_string(#roster{usj = {User, Server, JID},
name = Name,
subscription = Subscription,
ask = Ask}) ->
ask = Ask,
askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
Nick = ejabberd_odbc:escape(Name),
@@ -295,6 +296,7 @@ record_to_string(#roster{usj = {User, Server, JID},
"'", Nick, "',"
"'", SSubscription, "',"
"'", SAsk, "',"
"'", AskMessage, "',"
"'N', '', 'item')"].
groups_to_string(#roster{usj = {User, Server, JID},
+2 -1
View File
@@ -12,7 +12,8 @@ OUTDIR = ..
EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)/eldap.beam \
$(OUTDIR)/ELDAPv3.beam
$(OUTDIR)/ELDAPv3.beam \
$(OUTDIR)/eldap_filter.beam
all: $(OBJS)
+2 -1
View File
@@ -6,7 +6,8 @@ EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)\eldap.beam \
$(OUTDIR)\ELDAPv3.beam
$(OUTDIR)\ELDAPv3.beam \
$(OUTDIR)\eldap_filter.beam
ALL : $(OBJS)
+1 -1
View File
@@ -808,7 +808,7 @@ polish([], Res, Ref) ->
%%-----------------------------------------------------------------------
connect_bind(S) ->
Host = next_host(S#eldap.host, S#eldap.hosts),
TcpOpts = [{packet, asn1}, {active, true}, binary],
TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true}, binary],
case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of
{ok, Socket} ->
case bind_request(Socket, S) of
+285
View File
@@ -0,0 +1,285 @@
%%%====================================================
%%% File: eldap_filter.erl
%%% Purpose: Converts String Representation of
%%% LDAP Search Filter (RFC 2254)
%%% to eldap's representation of filter
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
%%% License: GPL
%%%====================================================
-module(eldap_filter).
-author('xram@jabber.ru').
%%%======================
%%% Export functions
%%%======================
-export([parse/1,
parse/2,
do_sub/2
]).
%%%-------------------------------------------------------------------------
%%% Arity: parse/1
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
%%% {error, bad_filter}
%%%
%%% RFC2254_Filter = string().
%%%
%%% Description: Converts String Representation of LDAP Search Filter (RFC 2254)
%%% to eldap's representation of filter.
%%%
%%% Example:
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
%%%
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
%%% {present,"mail"}]}}
%%%-------------------------------------------------------------------------
parse(RFC2254_Filter) ->
parse(RFC2254_Filter, []).
%%%-------------------------------------------------------------------------
%%% Arity: parse/2
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
%%% {ok, EldapFilter} |
%%% {error, bad_filter} |
%%% {error, bad_regexp} |
%%% {error, max_substitute_recursion}
%%%
%%% SubstValue = {RegExp, Value} | {RegExp, Value, N},
%%% RFC2254_Filter = RegExp = Value = string(),
%%% N = integer().
%%%
%%% Description: The same as parse/1, but substitutes N or all occurences
%%% of RegExp with Value *after* parsing.
%%%
%%% Example:
%%% > eldap_filter:parse(
%%% "(|(mail=%u@%d)(jid=%u@%d))",
%%% [{"%u", "xramtsov"},{"%d","gmail.com"}]).
%%%
%%% {ok,{'or',[{equalityMatch,{'AttributeValueAssertion',
%%% "mail",
%%% "xramtsov@gmail.com"}},
%%% {equalityMatch,{'AttributeValueAssertion',
%%% "jid",
%%% "xramtsov@gmail.com"}}]}}
%%%--------------------------------------------------------------------------
parse(RFC2254_Filter, ListOfSubValues) ->
case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
[EldapFilter] when is_tuple(EldapFilter) ->
{ok, EldapFilter};
{regexp, Error} ->
{error, Error};
_ ->
{error, bad_filter}
end.
%%%==========================
%%% Internal functions
%%%==========================
%%%----------------------
%%% split/1,4
%%%----------------------
split(Filter) ->
split(Filter, 0, [], []).
split([], _, _, Result) ->
Result;
split([H|T], Num, Rest, Result) ->
NewNum = case H of
$( -> Num + 1;
$) -> Num - 1;
_ -> Num
end,
if
NewNum == 0 ->
X = Rest++[H],
LenX = length(X),
if
LenX > 2 ->
split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
true ->
split(T, 0, Rest, Result)
end;
true ->
split(T, NewNum, Rest++[H], Result)
end.
%%%-----------------------
%%% parse_filter/1
%%%-----------------------
parse_filter(Filter) ->
case Filter of
[$! | T] ->
{'not', parse_filter(T)};
[$| | T] ->
{'or', parse_filter(T)};
[$& | T] ->
{'and', parse_filter(T)};
[$( | _] ->
parse_filter(split(Filter));
[List | _] when is_list(List) ->
[parse_filter(X) || X <- Filter];
_ ->
Filter
end.
%%%--------------------
%%% convert_filter/2
%%%--------------------
convert_filter({'not', [Val | _]}, Replace) ->
eldap:'not'(convert_filter(Val, Replace));
convert_filter({'or', Vals}, Replace) ->
eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
convert_filter({'and', Vals}, Replace) ->
eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
parse_attr(Filter, Replace);
convert_filter(Filter, Replace) when is_list(Filter) ->
[convert_filter(X, Replace) || X <- Filter].
%%%-----------------
%%% parse_attr/2,3
%%%-----------------
parse_attr(Attr, ListOfSubValues) ->
{Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
parse_attr(Action, {Name, Value}, ListOfSubValues).
parse_attr(approx, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:approxMatch(Name, NewValue);
parse_attr(greater, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:greaterOrEqual(Name, NewValue);
parse_attr(less, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:lessOrEqual(Name, NewValue);
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
[Head | Tail] when Tail /= [] ->
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
R ->
R
end,
case Pattern of
[V] ->
eldap:equalityMatch(Name, V);
{[], [], []} ->
eldap:present(Name);
{"", Any, ""} ->
eldap:substrings(Name, [{any, X} || X<-Any]);
{H, Any, ""} ->
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
{"", Any, T} ->
eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
{H, Any, T} ->
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
end;
parse_attr(_, _, _) ->
false.
%%%--------------------
%%% do_sub/2,3
%%%--------------------
-define(MAX_RECURSION, 100).
do_sub(S, []) ->
S;
do_sub([], _) ->
[];
do_sub(S, [{RegExp, New} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New)}, 1),
do_sub(Result, T);
do_sub(S, [{RegExp, New, Times} | T]) ->
Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1),
do_sub(Result, T).
do_sub(S, {RegExp, New}, Iter) ->
case regexp:sub(S, RegExp, New) of
{ok, NewS, 0} ->
NewS;
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
do_sub(NewS, {RegExp, New}, Iter+1);
{ok, _, _} when Iter > ?MAX_RECURSION ->
throw({regexp, max_substitute_recursion});
_ ->
throw({regexp, bad_regexp})
end;
do_sub(S, {_, _, N}, _) when N<1 ->
S;
do_sub(S, {RegExp, New, Times}, Iter) ->
case regexp:sub(S, RegExp, New) of
{ok, NewS, 0} ->
NewS;
{ok, NewS, _} when Iter < Times ->
do_sub(NewS, {RegExp, New, Times}, Iter+1);
{ok, NewS, _} ->
NewS;
_ ->
throw({regexp, bad_regexp})
end.
remove_extra_asterisks(String) ->
{Res, _} = lists:foldl(
fun(X, {Acc, Last}) ->
case X of
$* when Last==$* ->
{Acc, X};
_ ->
{Acc ++ [X], X}
end
end,
{"", ""}, String),
Res.
replace_amps(String) ->
lists:foldl(
fun(X, Acc) ->
if
X == $& ->
Acc ++ "\\&";
true ->
Acc ++ [X]
end
end,
"", String).
split_attribute(String) ->
split_attribute(String, "", $0).
split_attribute([], _, _) ->
{error, "", ""};
split_attribute([H|Tail], Acc, Last) ->
case H of
$= when Last==$> ->
{greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$< ->
{less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$~ ->
{approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$: ->
{equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= ->
{equal, Acc, Tail};
_ ->
split_attribute(Tail, Acc++[H], H)
end.
+1 -1
View File
@@ -164,7 +164,7 @@ void *erlXML_CharacterDataHandler(expat_data *d,
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_CDATA);
ei_x_encode_string_len_fixed(&event_buf, s, len);
ei_x_encode_binary(&event_buf, s, len);
return NULL;
}
+114 -22
View File
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : gen_iq_handler.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Purpose : IQ handler support
%%% Created : 22 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
@@ -10,19 +10,35 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-export([start/0,
start_link/3,
-behaviour(gen_server).
%% API
-export([start_link/3,
add_iq_handler/6,
remove_iq_handler/3,
stop_iq_handler/3,
handle/7,
process_iq/6,
queue_init/3]).
process_iq/6]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
start() ->
ok.
-record(state, {host,
module,
function}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Module, Function) ->
gen_server:start_link(?MODULE, [Host, Module, Function], []).
add_iq_handler(Component, Host, NS, Module, Function, Type) ->
case Type of
@@ -33,6 +49,17 @@ add_iq_handler(Component, Host, NS, Module, Function, Type) ->
[Host, Module, Function]),
Component:register_iq_handler(Host, NS, Module, Function,
{one_queue, Pid});
{queues, N} ->
Pids =
lists:map(
fun(_) ->
{ok, Pid} = supervisor:start_child(
ejabberd_iq_sup,
[Host, Module, Function]),
Pid
end, lists:seq(1, N)),
Component:register_iq_handler(Host, NS, Module, Function,
{queues, Pids});
parallel ->
Component:register_iq_handler(Host, NS, Module, Function, parallel)
end.
@@ -40,10 +67,14 @@ add_iq_handler(Component, Host, NS, Module, Function, Type) ->
remove_iq_handler(Component, Host, NS) ->
Component:unregister_iq_handler(Host, NS).
stop_iq_handler(Module, Function, Opts) ->
stop_iq_handler(_Module, _Function, Opts) ->
case Opts of
{one_queue, Pid} ->
exit(Pid, kill);
gen_server:call(Pid, stop);
{queues, Pids} ->
lists:foreach(fun(Pid) ->
catch gen_server:call(Pid, stop)
end, Pids);
_ ->
ok
end.
@@ -54,6 +85,9 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
process_iq(Host, Module, Function, From, To, IQ);
{one_queue, Pid} ->
Pid ! {process_iq, From, To, IQ};
{queues, Pids} ->
Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids),
Pid ! {process_iq, From, To, IQ};
parallel ->
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
_ ->
@@ -75,18 +109,76 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
end
end.
start_link(Host, Module, Function) ->
{ok, proc_lib:spawn_link(?MODULE, queue_init, [Host, Module, Function])}.
%%====================================================================
%% gen_server callbacks
%%====================================================================
queue_init(Host, Module, Function) ->
queue_loop(Host, Module, Function).
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Module, Function]) ->
{ok, #state{host = Host,
module = Module,
function = Function}}.
% TODO: use gen_event
queue_loop(Host, Module, Function) ->
receive
{process_iq, From, To, IQ} ->
process_iq(Host, Module, Function, From, To, IQ),
queue_loop(Host, Module, Function);
_ ->
queue_loop(Host, Module, Function)
end.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
Reply = ok,
{stop, normal, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({process_iq, From, To, IQ},
#state{host = Host,
module = Module,
function = Function} = State) ->
process_iq(Host, Module, Function, From, To, IQ),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+5 -1
View File
@@ -19,7 +19,8 @@
loaded_modules/1,
loaded_modules_with_opts/1,
get_hosts/2,
get_module_proc/2]).
get_module_proc/2,
is_loaded/2]).
-export([behaviour_info/1]).
@@ -144,3 +145,6 @@ get_hosts(Opts, Prefix) ->
get_module_proc(Host, Base) ->
list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
+13 -4
View File
@@ -134,10 +134,19 @@ xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
end,
ok;
?NS_VCARD ->
catch mod_vcard:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El}),
case lists:member(mod_vcard_odbc,
gen_mod:loaded_modules(LServer)) of
true ->
catch mod_vcard_odbc:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El});
false ->
catch mod_vcard:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El})
end,
ok;
"jabber:x:offline" ->
process_offline(Server, From, El),
+3
View File
@@ -181,6 +181,9 @@ string_to_jid1([], "") ->
string_to_jid1([], N) ->
make_jid("", lists:reverse(N), "").
%% Only one "@" is admitted per JID
string_to_jid2([$@ | _J], _N, _S) ->
error;
string_to_jid2([$/ | _J], _N, "") ->
error;
string_to_jid2([$/ | J], N, S) ->
+4
View File
@@ -33,6 +33,7 @@
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_EJABBERD_CONFIG, "ejabberd:config").
@@ -48,6 +49,9 @@
-define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth").
-define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register").
-define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress").
-define(NS_COMPRESS, "http://jabber.org/protocol/compress").
% TODO: remove "code" attribute (currently it used for backward-compatibility)
-define(STANZA_ERROR(Code, Type, Condition),
+250
View File
@@ -0,0 +1,250 @@
%%%----------------------------------------------------------------------
%%% File : mod_adhoc.erl
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
%%% Purpose : Handle incoming ad-doc requests (JEP-0050)
%%% Created : 15 Nov 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_adhoc).
-author('henoch@dtek.chalmers.se').
-vsn('$Revision$ ').
-behaviour(gen_mod).
-export([start/2,
stop/1,
process_local_iq/3,
process_sm_iq/3,
get_local_commands/5,
get_local_identity/5,
get_local_features/5,
get_sm_commands/5,
get_sm_identity/5,
get_sm_features/5,
ping_item/4,
ping_command/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS,
?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 99),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, ping_item, 100),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, ping_command, 100).
stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_commands, 99),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 99),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS).
%-------------------------------------------------------------------------
get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "", Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
case Display of
false ->
Acc;
_ ->
Items = case Acc of
{result, I} -> I;
_ -> []
end,
Nodes = [{xmlelement,
"item",
[{"jid", Server},
{"node", ?NS_COMMANDS},
{"name", translate:translate(Lang, "Commands")}],
[]}],
{result, Items ++ Nodes}
end;
get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_local_items, LServer, {result, []}, [From, To, Lang]);
get_local_commands(_Acc, _From, _To, "ping", _Lang) ->
{result, []};
get_local_commands(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) ->
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
case Display of
false ->
Acc;
_ ->
Items = case Acc of
{result, I} -> I;
_ -> []
end,
Nodes = [{xmlelement,
"item",
[{"jid", jlib:jid_to_string(To)},
{"node", ?NS_COMMANDS},
{"name", translate:translate(Lang, "Commands")}],
[]}],
{result, Items ++ Nodes}
end;
get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]);
get_sm_commands(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list.
get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-list"},
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
get_local_identity(Acc, _From, _To, "ping", Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-node"},
{"name", translate:translate(Lang, "Ping")}], []} | Acc];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list.
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-list"},
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
get_local_features(Acc, _From, _To, "", _Lang) ->
Feats = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
%% override all lesser features...
{result, []};
get_local_features(_Acc, _From, _To, "ping", _Lang) ->
%% override all lesser features...
{result, [?NS_COMMANDS]};
get_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
get_sm_features(Acc, _From, _To, "", _Lang) ->
Feats = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
%% override all lesser features...
{result, []};
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
process_local_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_local_commands).
process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
#adhoc_request{} = AdhocRequest ->
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(Hook, Host, empty,
[From, To, AdhocRequest]) of
ignore ->
ignore;
empty ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
Command ->
IQ#iq{type = result, sub_el = [Command]}
end
end.
ping_item(Acc, _From, #jid{server = Server} = _To, Lang) ->
Items = case Acc of
{result, I} ->
I;
_ ->
[]
end,
Nodes = [{xmlelement, "item",
[{"jid", Server},
{"node", "ping"},
{"name", translate:translate(Lang, "Ping")}],
[]}],
{result, Items ++ Nodes}.
ping_command(_Acc, _From, _To,
#adhoc_request{lang = Lang,
node = "ping",
sessionid = _Sessionid,
action = Action} = Request) ->
if
Action == ""; Action == "execute" ->
adhoc:produce_response(
Request,
#adhoc_response{status = completed,
notes = [{"info", translate:translate(
Lang,
"Pong")}]});
true ->
{error, ?ERR_BAD_REQUEST}
end;
ping_command(Acc, _From, _To, _Request) ->
Acc.
+436 -7
View File
@@ -15,10 +15,16 @@
init/0,
stop/1,
announce/3,
send_motd/1]).
send_motd/1,
disco_identity/5,
disco_features/5,
disco_items/5,
announce_commands/4,
announce_items/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
-record(motd, {server, packet}).
-record(motd_users, {us, dummy = []}).
@@ -33,6 +39,11 @@ start(Host, _Opts) ->
update_tables(),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
ejabberd_hooks:add(user_available_hook, Host,
?MODULE, send_motd, 50),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
@@ -66,6 +77,11 @@ loop() ->
end.
stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
ejabberd_hooks:delete(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:delete(sm_register_connection_hook, Host,
@@ -74,6 +90,7 @@ stop(Host) ->
exit(whereis(Proc), stop),
{wait, Proc}.
% Announcing via messages to a custom resource
announce(From, To, Packet) ->
case To of
#jid{luser = "", lresource = Res} ->
@@ -105,12 +122,422 @@ announce(From, To, Packet) ->
ok
end.
%-------------------------------------------------------------------------
% Announcing via ad-hoc commands
-define(INFO_COMMAND(Lang, Node),
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-node"},
{"name", get_title(Lang, Node)}], []}]).
disco_identity(Acc, _From, _To, Node, Lang) ->
case Node of
"announce/all" ->
?INFO_COMMAND(Lang, Node);
"announce/all-hosts/online" ->
?INFO_COMMAND(Lang, Node);
"announce/online" ->
?INFO_COMMAND(Lang, Node);
"announce/motd" ->
?INFO_COMMAND(Lang, Node);
"announce/motd/delete" ->
?INFO_COMMAND(Lang, Node);
"announce/motd/update" ->
?INFO_COMMAND(Lang, Node);
_ ->
Acc
end.
%-------------------------------------------------------------------------
-define(INFO_RESULT(Allow, Feats),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
allow ->
{result, Feats}
end).
disco_features(Acc, From, #jid{lserver = LServer} = _To,
"announce", _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
{error, ?ERR_FORBIDDEN};
_ ->
{result, []}
end
end;
disco_features(Acc, From, #jid{lserver = LServer} = _To,
"announce/all-hosts/online", _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
Allow = acl:match_rule(global, Access, From),
?INFO_RESULT(Allow, [?NS_COMMANDS])
end;
disco_features(Acc, From, #jid{lserver = LServer} = _To,
Node, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Allow = acl:match_rule(LServer, Access, From),
case Node of
"announce/all" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
"announce/online" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
"announce/motd" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
"announce/motd/delete" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
"announce/motd/update" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
_ ->
Acc
end
end.
%-------------------------------------------------------------------------
-define(NODE_TO_ITEM(Lang, Server, Node),
{xmlelement, "item",
[{"jid", Server},
{"node", Node},
{"name", get_title(Lang, Node)}],
[]}).
-define(ITEMS_RESULT(Allow, Items),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
allow ->
{result, Items}
end).
disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
"", Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
Acc;
_ ->
Items = case Acc of
{result, I} -> I;
_ -> []
end,
Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")],
{result, Items ++ Nodes}
end
end;
disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
announce_items(Acc, From, To, Lang)
end;
disco_items(Acc, From, #jid{lserver = LServer} = _To,
"announce/all-hosts/online", _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
Allow = acl:match_rule(global, Access, From),
?ITEMS_RESULT(Allow, [])
end;
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
_ ->
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Allow = acl:match_rule(LServer, Access, From),
case Node of
"announce/all" ->
?ITEMS_RESULT(Allow, []);
"announce/online" ->
?ITEMS_RESULT(Allow, []);
"announce/motd" ->
?ITEMS_RESULT(Allow, []);
"announce/motd/delete" ->
?ITEMS_RESULT(Allow, []);
"announce/motd/update" ->
?ITEMS_RESULT(Allow, []);
_ ->
Acc
end
end.
%-------------------------------------------------------------------------
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Nodes1 = case acl:match_rule(LServer, Access1, From) of
allow ->
[?NODE_TO_ITEM(Lang, Server, "announce/all"),
?NODE_TO_ITEM(Lang, Server, "announce/online"),
?NODE_TO_ITEM(Lang, Server, "announce/motd"),
?NODE_TO_ITEM(Lang, Server, "announce/motd/delete"),
?NODE_TO_ITEM(Lang, Server, "announce/motd/update")];
deny ->
[]
end,
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
Nodes2 = case acl:match_rule(global, Access2, From) of
allow ->
[?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/online")];
deny ->
[]
end,
case {Nodes1, Nodes2} of
{[], []} ->
Acc;
_ ->
Items = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Items ++ Nodes1 ++ Nodes2}
end.
%-------------------------------------------------------------------------
-define(COMMANDS_RESULT(Allow, From, To, Request),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
allow ->
announce_commands(From, To, Request)
end).
announce_commands(_Acc, From, To,
#adhoc_request{
node = "announce/all-hosts/online"} = Request) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
Allow = acl:match_rule(global, Access, From),
?COMMANDS_RESULT(Allow, From, To, Request);
announce_commands(Acc, From, #jid{lserver = LServer} = To,
#adhoc_request{node = Node} = Request) ->
Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Allow = acl:match_rule(LServer, Access, From),
case Node of
"announce/all" ->
?COMMANDS_RESULT(Allow, From, To, Request);
"announce/online" ->
?COMMANDS_RESULT(Allow, From, To, Request);
"announce/motd" ->
?COMMANDS_RESULT(Allow, From, To, Request);
"announce/motd/delete" ->
?COMMANDS_RESULT(Allow, From, To, Request);
"announce/motd/update" ->
?COMMANDS_RESULT(Allow, From, To, Request);
_ ->
Acc
end.
%-------------------------------------------------------------------------
announce_commands(From, To,
#adhoc_request{lang = Lang,
node = Node,
action = Action,
xdata = XData} = Request) ->
%% If the "action" attribute is not present, it is
%% understood as "execute". If there was no <actions/>
%% element in the first response (which there isn't in our
%% case), "execute" and "complete" are equivalent.
ActionIsExecute = lists:member(Action,
["", "execute", "complete"]),
if Action == "cancel" ->
%% User cancels request
adhoc:produce_response(Request,
#adhoc_response{status = canceled});
XData == false, ActionIsExecute ->
%% User requests form
adhoc:produce_response(
Request,
#adhoc_response{status = executing,
elements = [generate_adhoc_form(Lang, Node)]});
XData /= false, ActionIsExecute ->
%% User returns form.
case jlib:parse_xdata_submit(XData) of
invalid ->
{error, ?ERR_BAD_REQUEST};
Fields ->
handle_adhoc_form(From, To, Request, Fields)
end;
true ->
{error, ?ERR_BAD_REQUEST}
end.
generate_adhoc_form(Lang, Node) ->
{xmlelement, "x",
[{"xmlns", ?NS_XDATA},
{"type", "form"}],
[{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
++
if Node == "announce/motd/delete" ->
[{xmlelement, "field",
[{"var", "confirm"},
{"type", "boolean"},
{"label", translate:translate(Lang, "Really delete message of the day?")}],
[{xmlelement, "value",
[],
[{xmlcdata, "true"}]}]}];
true ->
[{xmlelement, "field",
[{"var", "subject"},
{"type", "text-single"},
{"label", translate:translate(Lang, "Subject")}],
[]},
{xmlelement, "field",
[{"var", "body"},
{"type", "text-multi"},
{"label", translate:translate(Lang, "Message body")}],
[]}]
end}.
join_lines([]) ->
[];
join_lines(Lines) ->
join_lines(Lines, []).
join_lines([Line|Lines], Acc) ->
join_lines(Lines, ["\n",Line|Acc]);
join_lines([], Acc) ->
%% Remove last newline
lists:flatten(lists:reverse(tl(Acc))).
handle_adhoc_form(From, #jid{lserver = LServer} = To,
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID},
Fields) ->
Confirm = case lists:keysearch("confirm", 1, Fields) of
{value, {"confirm", ["true"]}} ->
true;
{value, {"confirm", ["1"]}} ->
true;
_ ->
false
end,
Subject = case lists:keysearch("subject", 1, Fields) of
{value, {"subject", SubjectLines}} ->
%% There really shouldn't be more than one
%% subject line, but can we stop them?
join_lines(SubjectLines);
_ ->
[]
end,
Body = case lists:keysearch("body", 1, Fields) of
{value, {"body", BodyLines}} ->
join_lines(BodyLines);
_ ->
[]
end,
Response = #adhoc_response{lang = Lang,
node = Node,
sessionid = SessionID,
status = completed},
Packet = {xmlelement, "message", [{"type", "normal"}],
if Subject /= [] ->
[{xmlelement, "subject", [],
[{xmlcdata, Subject}]}];
true ->
[]
end ++
if Body /= [] ->
[{xmlelement, "body", [],
[{xmlcdata, Body}]}];
true ->
[]
end},
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
case {Node, Body} of
{"announce/motd/delete", _} ->
if Confirm ->
Proc ! {announce_motd_delete, From, To, Packet},
adhoc:produce_response(Response);
true ->
adhoc:produce_response(Response)
end;
{_, []} ->
%% An announce message with no body is definitely an operator error.
%% Throw an error and give him/her a chance to send message again.
{error, ?ERRT_NOT_ACCEPTABLE(
Lang,
"No body provided for announce message")};
%% Now send the packet to ?PROCNAME.
%% We don't use direct announce_* functions because it
%% leads to large delay in response and <iq/> queries processing
{"announce/all", _} ->
Proc ! {announce_all, From, To, Packet},
adhoc:produce_response(Response);
{"announce/online", _} ->
Proc ! {announce_online, From, To, Packet},
adhoc:produce_response(Response);
{"announce/all-hosts/online", _} ->
Proc ! {announce_all_hosts_online, From, To, Packet},
adhoc:produce_response(Response);
{"announce/motd", _} ->
Proc ! {announce_motd, From, To, Packet},
adhoc:produce_response(Response);
{"announce/motd/update", _} ->
Proc ! {announce_motd_update, From, To, Packet},
adhoc:produce_response(Response);
_ ->
%% This can't happen, as we haven't registered any other
%% command nodes.
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
get_title(Lang, "announce") ->
translate:translate(Lang, "Announcements");
get_title(Lang, "announce/all") ->
translate:translate(Lang, "Send announcement to all users");
get_title(Lang, "announce/online") ->
translate:translate(Lang, "Send announcement to all online users");
get_title(Lang, "announce/all-hosts/online") ->
translate:translate(Lang, "Send announcement to all online users on all hosts");
get_title(Lang, "announce/motd") ->
translate:translate(Lang, "Set message of the day and send to online users");
get_title(Lang, "announce/motd/update") ->
translate:translate(Lang, "Update message of the day (don't send)");
get_title(Lang, "announce/motd/delete") ->
translate:translate(Lang, "Delete message of the day").
%-------------------------------------------------------------------------
announce_all(From, To, Packet) ->
Host = To#jid.lserver,
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
Local = jlib:make_jid("", To#jid.server, ""),
@@ -126,7 +553,7 @@ announce_online(From, To, Packet) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
@@ -138,7 +565,7 @@ announce_all_hosts_online(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
@@ -159,7 +586,7 @@ announce_motd(From, To, Packet) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(To#jid.lserver, Packet),
@@ -179,7 +606,7 @@ announce_motd_update(From, To, Packet) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Host, Packet)
@@ -197,7 +624,7 @@ announce_motd_delete(From, To, Packet) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete(Host)
@@ -237,6 +664,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
ok
end.
%-------------------------------------------------------------------------
update_tables() ->
update_motd_table(),
@@ -326,3 +754,4 @@ update_motd_users_table() ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.
+469 -298
View File
File diff suppressed because it is too large Load Diff
+15 -26
View File
@@ -113,8 +113,7 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} =
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
SNode = xml:get_tag_attr_s("node", SubEl),
Node = string:tokens(SNode, "/"),
Node = xml:get_tag_attr_s("node", SubEl),
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items,
@@ -123,8 +122,8 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} =
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
[] -> [];
_ -> [{"node", SNode}]
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@@ -144,8 +143,7 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
SNode = xml:get_tag_attr_s("node", SubEl),
Node = string:tokens(SNode, "/"),
Node = xml:get_tag_attr_s("node", SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host,
[],
@@ -156,8 +154,8 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
[] -> [];
_ -> [{"node", SNode}]
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@@ -252,7 +250,7 @@ get_vh_services(Host) ->
process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
#jid{user = User, luser = LTo, lserver = ToServer} = To,
#jid{luser = LTo, lserver = ToServer} = To,
#jid{luser = LFrom, lserver = LServer} = From,
Self = (LTo == LFrom) andalso (ToServer == LServer),
Node = xml:get_tag_attr_s("node", SubEl),
@@ -272,16 +270,15 @@ process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ
end;
get ->
Host = To#jid.lserver,
SNode = xml:get_tag_attr_s("node", SubEl),
Node = string:tokens(SNode, "/"),
Node = xml:get_tag_attr_s("node", SubEl),
case ejabberd_hooks:run_fold(disco_sm_items,
Host,
empty,
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
[] -> [];
_ -> [{"node", SNode}]
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@@ -329,8 +326,7 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
SNode = xml:get_tag_attr_s("node", SubEl),
Node = string:tokens(SNode, "/"),
Node = xml:get_tag_attr_s("node", SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host,
[],
@@ -341,8 +337,8 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
[] -> [];
_ -> [{"node", SNode}]
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@@ -384,24 +380,17 @@ get_user_resources(User, Server) ->
get_publish_items(empty,
#jid{luser = LFrom, lserver = LSFrom},
#jid{user = User, server = Server, luser = LTo, lserver = LSTo} = _To,
#jid{luser = LTo, lserver = LSTo} = _To,
Node, _Lang) ->
if
(LFrom == LTo) and (LSFrom == LSTo) ->
% Hack
SNode = join(Node, "/"),
retrieve_disco_publish({LTo, LSTo}, SNode);
retrieve_disco_publish({LTo, LSTo}, Node);
true ->
empty
end;
get_publish_items(Acc, _From, _To, _Node, _Lang) ->
Acc.
join(List, Sep) ->
lists:foldl(fun(A, "") -> A;
(A, Acc) -> Acc ++ Sep ++ A
end, "", List).
process_disco_publish(User, Node, Items) ->
F = fun() ->
lists:foreach(
+101 -22
View File
@@ -10,38 +10,117 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
-behaviour(gen_mod).
-export([start/2, init/1, stop/1]).
%% API
-export([start_link/2, start/2, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {host}).
-define(PROCNAME, ejabberd_mod_echo).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost])).
init(Host) ->
ejabberd_router:register_route(Host),
loop(Host).
loop(Host) ->
receive
{route, From, To, Packet} ->
ejabberd_router:route(To, From, Packet),
loop(Host);
stop ->
ejabberd_router:unregister_route(Host),
ok;
_ ->
loop(Host)
end.
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
gen_server:call(Proc, stop),
supervisor:stop_child(ejabberd_sup, Proc).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
ejabberd_router:register_route(MyHost),
{ok, #state{host = MyHost}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
ejabberd_router:route(To, From, Packet),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+8 -1
View File
@@ -6,6 +6,13 @@ CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ @ERLANG_LIBS@ @LIBICONV@
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
else
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
endif
SUBDIRS =
ERLSHLIBS = ../iconv_erl.so
@@ -28,7 +35,7 @@ $(OUTDIR)/%.beam: %.erl
$(ERLSHLIBS): ../%.so: %.c
$(CC) -Wall $(INCLUDES) $(CFLAGS) $(LDFLAGS) \
$(subst ../,,$(subst .so,.c,$@)) $(LIBS) \
-o $@ -fpic -shared
-o $@ $(DYNAMIC_LIB_CFLAGS)
clean:
rm -f $(OBJS) $(ERLSHLIBS)
+18 -10
View File
@@ -55,27 +55,35 @@ static int iconv_erl_control(ErlDrvData drv_data,
ei_get_type(buf, &index, &i, &size);
stmp = string = malloc(size + 1);
ei_decode_string(buf, &index, string);
cd = iconv_open(to, from);
if(cd == (iconv_t) -1)
{
if (cd == (iconv_t) -1) {
cd = iconv_open("ascii", "ascii");
if(cd == (iconv_t) -1)
{
cd = iconv_open("ascii", "ascii");
}
if (cd == (iconv_t) -1) {
*rbuf = (char*)(b = driver_alloc_binary(size));
memcpy(b->orig_bytes, string, size);
free(from);
free(to);
free(string);
return size;
}
}
outleft = avail = 4*size;
inleft = size;
rtmp = rstring = malloc(avail);
iconv(cd, &stmp, &inleft, &rtmp, &outleft);
while (inleft > 0) {
if (iconv(cd, &stmp, &inleft, &rtmp, &outleft) == (size_t) -1) {
stmp++;
inleft--;
}
}
size = rtmp - rstring;
//printf("size=%d, res=%s\r\n", size, rstring);
*rbuf = (char*)(b = driver_alloc_binary(size));
memcpy(b->orig_bytes, rstring, size);
+137 -34
View File
@@ -10,12 +10,20 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
-behaviour(gen_mod).
-export([start/2, init/2, stop/1,
%% API
-export([start_link/2,
start/2,
stop/1,
closed_connection/3,
get_user_and_encoding/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -24,9 +32,51 @@
-record(irc_connection, {jid_server_host, pid}).
-record(irc_custom, {us_host, data}).
-record(state, {host, server_host, access}).
-define(PROCNAME, ejabberd_mod_irc).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
stop_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
iconv:start(),
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
@@ -34,38 +84,97 @@ start(Host, Opts) ->
MyHost = gen_mod:get_opt(host, Opts, "irc." ++ Host),
update_table(MyHost),
Access = gen_mod:get_opt(access, Opts, all),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost, Access])).
init(Host, Access) ->
catch ets:new(irc_connection, [named_table,
public,
{keypos, #irc_connection.jid_server_host}]),
ejabberd_router:register_route(Host),
loop(Host, Access).
ejabberd_router:register_route(MyHost),
{ok, #state{host = MyHost,
server_host = Host,
access = Access}}.
loop(Host, Access) ->
receive
{route, From, To, Packet} ->
case catch do_route(Host, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, Access);
stop ->
ejabberd_router:unregister_route(Host),
ok;
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
#state{host = Host,
server_host = ServerHost,
access = Access} = State) ->
case catch do_route(Host, ServerHost, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
loop(Host, Access)
end.
ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
do_route(Host, Access, From, To, Packet) ->
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
start_supervisor(Host) ->
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup),
ChildSpec =
{Proc,
{ejabberd_tmp_sup, start_link,
[Proc, mod_irc_connection]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop_supervisor(Host) ->
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
do_route(Host, ServerHost, Access, From, To, Packet) ->
case acl:match_rule(Host, Access, From) of
allow ->
do_route1(Host, From, To, Packet);
do_route1(Host, ServerHost, From, To, Packet);
_ ->
{xmlelement, _Name, Attrs, _Els} = Packet,
Lang = xml:get_attr_s("xml:lang", Attrs),
@@ -75,7 +184,7 @@ do_route(Host, Access, From, To, Packet) ->
ejabberd_router:route(To, From, Err)
end.
do_route1(Host, From, To, Packet) ->
do_route1(Host, ServerHost, From, To, Packet) ->
#jid{user = ChanServ, resource = Resource} = To,
{xmlelement, _Name, Attrs, _Els} = Packet,
case ChanServ of
@@ -132,7 +241,7 @@ do_route1(Host, From, To, Packet) ->
{Username, Encoding} = get_user_and_encoding(
Host, From, Server),
{ok, Pid} = mod_irc_connection:start(
From, Host, Server,
From, Host, ServerHost, Server,
Username, Encoding),
ets:insert(
irc_connection,
@@ -174,12 +283,6 @@ do_route1(Host, From, To, Packet) ->
end.
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
closed_connection(Host, From, Server) ->
ets:delete(irc_connection, {From, Server, Host}).
@@ -188,7 +291,7 @@ iq_disco() ->
[{xmlelement, "identity",
[{"category", "conference"},
{"type", "irc"},
{"name", "ejabberd/mod_irc"}], []},
{"name", "IRC Transport"}], []},
{xmlelement, "feature",
[{"var", ?NS_MUC}], []},
{xmlelement, "feature",
@@ -204,7 +307,7 @@ iq_get_vcard(Lang) ->
"http://ejabberd.jabberstudio.org/"}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(Lang, "ejabberd IRC module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
process_register(Host, From, To, #iq{} = IQ) ->
case catch process_irc_register(Host, From, To, IQ) of
+184 -90
View File
@@ -13,7 +13,7 @@
-behaviour(gen_fsm).
%% External exports
-export([start/5, receiver/2, route_chan/4, route_nick/3]).
-export([start_link/5, start/6, route_chan/4, route_nick/3]).
%% gen_fsm callbacks
-export([init/1,
@@ -31,7 +31,7 @@
-define(SETS, gb_sets).
-record(state, {socket, encoding, receiver, queue,
-record(state, {socket, encoding, queue,
user, host, server, nick,
channels = dict:new(),
inbuf = "", outbuf = ""}).
@@ -47,8 +47,14 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(From, Host, Server, Username, Encoding) ->
gen_fsm:start(?MODULE, [From, Host, Server, Username, Encoding], ?FSMOPTS).
start(From, Host, ServerHost, Server, Username, Encoding) ->
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_irc_sup),
supervisor:start_child(
Supervisor, [From, Host, Server, Username, Encoding]).
start_link(From, Host, Server, Username, Encoding) ->
gen_fsm:start_link(?MODULE, [From, Host, Server, Username, Encoding],
?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -109,7 +115,7 @@ open_socket(init, StateData) ->
wait_for_registration(closed, StateData) ->
{stop, normal, StateData}.
stream_established({xmlstreamend, Name}, StateData) ->
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established(timeout, StateData) ->
@@ -139,7 +145,7 @@ stream_established(closed, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(Event, StateName, StateData) ->
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
@@ -151,11 +157,11 @@ handle_event(Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(Event, From, StateName, StateData) ->
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(OldVsn, StateName, StateData, Extra) ->
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-define(SEND(S),
@@ -237,15 +243,36 @@ handle_info({route_chan, Channel, Resource,
case Body of
"/quote " ++ Rest ->
?SEND(Rest ++ "\r\n");
"/msg " ++ Rest ->
?SEND("PRIVMSG " ++ Rest ++ "\r\n");
"/me " ++ Rest ->
Strings = string:tokens(Rest, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
io_lib:format(
"PRIVMSG #~s :\001ACTION ~s\001\r\n",
[Channel, S])
end, Strings)),
?SEND(Res);
"/ctcp " ++ Rest ->
Words = string:tokens(Rest, " "),
case Words of
[CtcpDest | _] ->
CtcpCmd =
toupper(
string:substr(
Rest,
string:str(Rest, " ") + 1)),
Res = io_lib:format(
"PRIVMSG ~s :\001~s\001\r\n",
[CtcpDest, CtcpCmd]),
?SEND(Res);
_ ->
ok
end;
_ ->
Body1 =
case Body of
[$/, $m, $e, $ | Rest] ->
"\001ACTION " ++ Rest ++ "\001";
_ ->
Body
end,
Strings = string:tokens(Body1, "\n"),
Strings = string:tokens(Body, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
@@ -265,19 +292,40 @@ handle_info({route_chan, Channel, Resource,
end, Strings)),
?SEND(Res)
end;
"chat" ->
Type when Type == "chat"; Type == ""; Type == "normal" ->
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
case Body of
"/quote " ++ Rest ->
?SEND(Rest ++ "\r\n");
"/msg " ++ Rest ->
?SEND("PRIVMSG " ++ Rest ++ "\r\n");
"/me " ++ Rest ->
Strings = string:tokens(Rest, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
io_lib:format(
"PRIVMSG ~s :\001ACTION ~s\001\r\n",
[Resource, S])
end, Strings)),
?SEND(Res);
"/ctcp " ++ Rest ->
Words = string:tokens(Rest, " "),
case Words of
[CtcpDest | _ ] ->
CtcpCmd =
toupper(
string:substr(
Rest, string:str(Rest, " ") + 1)),
Res = io_lib:format(
"PRIVMSG ~s :~s\r\n",
[CtcpDest, "\001" ++ CtcpCmd ++ "\001"]),
?SEND(Res);
_ ->
ok
end;
_ ->
Body1 = case Body of
[$/, $m, $e, $ | Rest] ->
"\001ACTION " ++ Rest ++ "\001";
_ ->
Body
end,
Strings = string:tokens(Body1, "\n"),
Strings = string:tokens(Body, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
@@ -352,14 +400,32 @@ handle_info({route_nick, Nick,
case Body of
"/quote " ++ Rest ->
?SEND(Rest ++ "\r\n");
"/msg " ++ Rest ->
?SEND("PRIVMSG " ++ Rest ++ "\r\n");
"/me " ++ Rest ->
Strings = string:tokens(Rest, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
io_lib:format(
"PRIVMSG ~s :\001ACTION ~s\001\r\n",
[Nick, S])
end, Strings)),
?SEND(Res);
"/ctcp " ++ Rest ->
Words = string:tokens(Rest, " "),
case Words of
[CtcpDest | _ ] ->
CtcpCmd = toupper(string:substr(Rest, string:str(Rest, " ")+1 )),
Res = io_lib:format(
"PRIVMSG ~s :~s\r\n",
[CtcpDest, "\001" ++ CtcpCmd ++ "\001"]),
?SEND(Res);
_ ->
ok
end;
_ ->
Body1 = case Body of
[$/, $m, $e, $ | Rest] ->
"\001ACTION " ++ Rest ++ "\001";
_ ->
Body
end,
Strings = string:tokens(Body1, "\n"),
Strings = string:tokens(Body, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
@@ -397,6 +463,9 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) ->
[_, "332", Nick, [$# | Chan] | _] ->
process_channel_topic(StateData, Chan, String),
StateData;
[_, "333", Nick, [$# | Chan] | _] ->
process_channel_topic_who(StateData, Chan, String),
StateData;
[_, "318", _, Nick | _] ->
process_endofwhois(StateData, String, Nick),
StateData;
@@ -445,7 +514,7 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) ->
"member", "participant"),
StateData;
[From, "KICK", [$# | Chan], Nick | _] ->
process_kick(StateData, Chan, From, Nick),
process_kick(StateData, Chan, From, Nick, String),
StateData;
[From, "NICK", Nick | _] ->
process_nick(StateData, From, Nick);
@@ -523,21 +592,6 @@ terminate(Reason, StateName, StateData) ->
%%% Internal functions
%%%----------------------------------------------------------------------
receiver(Socket, C2SPid) ->
XMLStreamPid = xml_stream:start(C2SPid),
receiver(Socket, C2SPid, XMLStreamPid).
receiver(Socket, C2SPid, XMLStreamPid) ->
case gen_tcp:recv(Socket, 0) of
{ok, Text} ->
xml_stream:send_text(XMLStreamPid, Text),
receiver(Socket, C2SPid, XMLStreamPid);
{error, Reason} ->
exit(XMLStreamPid, closed),
gen_fsm:send_event(C2SPid, closed),
ok
end.
send_text(#state{socket = Socket, encoding = Encoding}, Text) ->
CText = iconv:convert("utf-8", Encoding, lists:flatten(Text)),
%?DEBUG("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]),
@@ -611,6 +665,9 @@ process_channel_list_user(StateData, Chan, User) ->
case User1 of
[$@ | U2] -> {U2, "admin", "moderator"};
[$+ | U2] -> {U2, "member", "participant"};
[$\% | U2] -> {U2, "admin", "moderator"};
[$& | U2] -> {U2, "admin", "moderator"};
[$~ | U2] -> {U2, "admin", "moderator"};
_ -> {User1, "member", "participant"}
end,
ejabberd_router:route(
@@ -635,16 +692,43 @@ process_channel_list_user(StateData, Chan, User) ->
process_channel_topic(StateData, Chan, String) ->
FromUser = "someone",
{ok, Msg, _} = regexp:sub(String, ".*332[^:]*:", ""),
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, FromUser),
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "subject", [], [{xmlcdata, Msg1}]}]}).
[{xmlelement, "subject", [], [{xmlcdata, Msg1}]},
{xmlelement, "body", [], [{xmlcdata, "Topic for #" ++ Chan ++ ": " ++ Msg1}]}
]}).
process_channel_topic_who(StateData, Chan, String) ->
Words = string:tokens(String, " "),
Msg1 = case Words of
[_, "333", _, _Chan, Whoset , Timeset] ->
case string:to_integer(Timeset) of
{Unixtimeset, Rest} ->
"Topic for #" ++ Chan ++ " set by " ++ Whoset ++
" at " ++ unixtime2string(Unixtimeset);
_->
"Topic for #" ++ Chan ++ " set by " ++ Whoset
end;
[_, "333", _, _Chan, Whoset | _] ->
"Topic for #" ++ Chan ++ " set by " ++ Whoset;
_ ->
String
end,
Msg2 = filter_message(Msg1),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
process_endofwhois(StateData, String, Nick) ->
@@ -717,7 +801,7 @@ process_channotice(StateData, Chan, From, String) ->
[1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
"/me " ++ Rest;
_ ->
Msg
"/me NOTICE: " ++ Msg
end,
Msg2 = filter_message(Msg1),
ejabberd_router:route(
@@ -725,7 +809,7 @@ process_channotice(StateData, Chan, From, String) ->
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, "NOTICE: " ++ Msg2}]}]}).
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
@@ -755,7 +839,7 @@ process_notice(StateData, Nick, From, String) ->
[1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
"/me " ++ Rest;
_ ->
Msg
"/me NOTICE: " ++ Msg
end,
Msg2 = filter_message(Msg1),
ejabberd_router:route(
@@ -763,7 +847,7 @@ process_notice(StateData, Nick, From, String) ->
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [], [{xmlcdata, "NOTICE: " ++ Msg2}]}]}).
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
process_version(StateData, Nick, From) ->
@@ -785,7 +869,7 @@ process_userinfo(StateData, Nick, From) ->
send_text(
StateData,
io_lib:format("NOTICE ~s :\001USERINFO "
"This user uses xmpp:~s"
"xmpp:~s"
"\001\r\n",
[FromUser,
jlib:jid_to_string(StateData#state.user)])).
@@ -807,17 +891,8 @@ process_topic(StateData, Chan, From, String) ->
process_part(StateData, Chan, From, String) ->
[FromUser | FromIdent] = string:tokens(From, "!"),
{ok, Msg, _} = regexp:sub(String, ".*PART[^:]*", ""),
{ok, Msg, _} = regexp:sub(String, ".*PART[^:]*:", ""),
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
[{xmlcdata, "/me has part: " ++
Msg1 ++ "(" ++ FromIdent ++ ")" }]}]}),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, FromUser),
@@ -829,7 +904,7 @@ process_part(StateData, Chan, From, String) ->
{"role", "none"}],
[]}]},
{xmlelement, "status", [],
[{xmlcdata, Msg1 ++ "(" ++ FromIdent ++ ")"}]}]
[{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}]
}),
case catch dict:update(Chan,
fun(Ps) ->
@@ -852,16 +927,6 @@ process_quit(StateData, From, String) ->
fun(Chan, Ps) ->
case ?SETS:is_member(FromUser, Ps) of
true ->
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
[{xmlcdata, "/me has quit: " ++
Msg1 ++ "(" ++ FromIdent ++ ")" }]}]}),
ejabberd_router:route(
jlib:make_jid(
lists:concat([Chan, "%", StateData#state.server]),
@@ -874,7 +939,7 @@ process_quit(StateData, From, String) ->
{"role", "none"}],
[]}]},
{xmlelement, "status", [],
[{xmlcdata, Msg1 ++ "(" ++ FromIdent ++ ")"}]}
[{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}
]}),
remove_element(FromUser, Ps);
_ ->
@@ -899,16 +964,6 @@ process_join(StateData, Channel, From, String) ->
[]}]},
{xmlelement, "status", [],
[{xmlcdata, FromIdent}]}]}),
{ok, Msg, _} = regexp:sub(String, ".*JOIN[^:]*:", ""),
Msg1 = filter_message(Msg),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, FromUser),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
[{xmlcdata, "/me has joined " ++
Msg1 ++ "(" ++ FromIdent ++ ")" }]}]}),
case catch dict:update(Chan,
fun(Ps) ->
@@ -935,8 +990,15 @@ process_mode_o(StateData, Chan, From, Nick, Affiliation, Role) ->
{"role", Role}],
[]}]}]}).
process_kick(StateData, Chan, From, Nick) ->
%Msg = lists:last(string:tokens(String, ":")),
process_kick(StateData, Chan, From, Nick, String) ->
Msg = lists:last(string:tokens(String, ":")),
Msg2 = Nick ++ " kicked by " ++ From ++ " (" ++ filter_message(Msg) ++ ")",
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, ""),
StateData#state.user,
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}),
ejabberd_router:route(
jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]),
StateData#state.host, Nick),
@@ -1095,4 +1157,36 @@ filter_message(Msg) ->
false;
true -> true
end
end, Msg).
end, filter_mirc_colors(Msg)).
filter_mirc_colors(Msg) ->
case regexp:gsub(Msg, "(\\003[0-9]+)(,[0-9]+)?", "") of
{ok, Msg2, _} ->
Msg2;
_ ->
Msg
end.
unixtime2string(Unixtime) ->
Secs = Unixtime + calendar:datetime_to_gregorian_seconds(
{{1970, 1, 1}, {0,0,0}}),
case calendar:universal_time_to_local_time(
calendar:gregorian_seconds_to_datetime(Secs)) of
{{Year, Month, Day}, {Hour, Minute, Second}} ->
lists:flatten(
io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second]));
_->
"0000-00-00 00:00:00"
end.
toupper([C | Cs]) ->
if
C >= $a, C =< $z ->
[C - 32 | toupper(Cs)];
true ->
[C | toupper(Cs)]
end;
toupper([]) ->
[].
+3 -18
View File
@@ -56,7 +56,6 @@ process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
[]}]}
end.
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
@@ -96,10 +95,7 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
get_last(IQ, SubEl, LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
["select seconds, state from last "
"where username='", Username, "'"]) of
case catch odbc_queries:get_last(LServer, Username) of
{'EXIT', _Reason} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
{selected, ["seconds","state"], []} ->
@@ -121,8 +117,6 @@ get_last(IQ, SubEl, LUser, LServer) ->
end
end.
on_presence_update(User, Server, _Resource, Status) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
@@ -134,20 +128,11 @@ store_last_info(User, Server, TimeStamp, Status) ->
Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
State = ejabberd_odbc:escape(Status),
ejabberd_odbc:sql_query(
LServer,
["begin;"
"delete from last where username='", Username, "';"
"insert into last(username, seconds, state) "
"values ('", Username, "', '", Seconds, "', '", State, "');",
"commit"]).
odbc_queries:set_last_t(LServer, Username, Seconds, State).
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_query(
LServer,
["delete from last where username='", Username, "'"]).
odbc_queries:del_last(LServer, Username).
+1
View File
@@ -12,6 +12,7 @@ OUTDIR = ..
EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)/mod_muc.beam \
$(OUTDIR)/mod_muc_log.beam \
$(OUTDIR)/mod_muc_room.beam
all: $(OBJS)
+4
View File
@@ -6,6 +6,7 @@ EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)\mod_muc.beam \
$(OUTDIR)\mod_muc_log.beam \
$(OUTDIR)\mod_muc_room.beam
ALL : $(OBJS)
@@ -16,5 +17,8 @@ CLEAN :
$(OUTDIR)\mod_muc.beam : mod_muc.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc.erl
$(OUTDIR)\mod_muc_log.beam : mod_muc_log.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_log.erl
$(OUTDIR)\mod_muc_room.beam : mod_muc_room.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_muc_room.erl
+266 -159
View File
@@ -10,10 +10,12 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
-behaviour(gen_mod).
-export([start/2,
init/3,
%% API
-export([start_link/2,
start/2,
stop/1,
room_destroyed/3,
store_room/3,
@@ -22,6 +24,10 @@
process_iq_disco_items/4,
can_use_nick/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -30,9 +36,106 @@
-record(muc_online_room, {name_host, pid}).
-record(muc_registered, {us_host, nick}).
-record(state, {host, server_host, access, history_size}).
-define(PROCNAME, ejabberd_mod_muc).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
stop_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc).
room_destroyed(Host, Room, ServerHost) ->
gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
{room_destroyed, {Room, Host}},
ok.
store_room(Host, Name, Opts) ->
F = fun() ->
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
mnesia:transaction(F).
restore_room(Host, Name) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
[#muc_room{opts = Opts}] ->
Opts;
_ ->
error
end.
forget_room(Host, Name) ->
F = fun() ->
mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F).
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS}],
iq_disco_items(Host, From, Lang)}]},
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res)).
can_use_nick(_Host, _JID, "") ->
false;
can_use_nick(Host, JID, Nick) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
case catch mnesia:dirty_select(
muc_registered,
[{#muc_registered{us_host = '$1',
nick = Nick,
_ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]) of
{'EXIT', _Reason} ->
true;
[] ->
true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
mnesia:create_table(muc_room,
[{disc_copies, [node()]},
{attributes, record_info(fields, muc_room)}]),
@@ -45,46 +148,106 @@ start(Host, Opts) ->
Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init,
[MyHost, Host, {Access, AccessCreate, AccessAdmin}])).
init(Host, ServerHost, Access) ->
HistorySize = gen_mod:get_opt(history_size, Opts, 20),
catch ets:new(muc_online_room, [named_table,
public,
{keypos, #muc_online_room.name_host}]),
ejabberd_router:register_route(Host),
load_permanent_rooms(Host, ServerHost, Access),
loop(Host, ServerHost, Access).
ejabberd_router:register_route(MyHost),
load_permanent_rooms(MyHost, Host, {Access, AccessCreate, AccessAdmin},
HistorySize),
{ok, #state{host = MyHost,
server_host = Host,
access = {Access, AccessCreate, AccessAdmin},
history_size = HistorySize}}.
loop(Host, ServerHost, Access) ->
receive
{route, From, To, Packet} ->
case catch do_route(Host, ServerHost, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, ServerHost, Access);
{room_destroyed, RoomHost} ->
ets:delete(muc_online_room, RoomHost),
loop(Host, ServerHost, Access);
stop ->
ejabberd_router:unregister_route(Host),
ok;
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
#state{host = Host,
server_host = ServerHost,
access = Access,
history_size = HistorySize} = State) ->
case catch do_route(Host, ServerHost, Access, HistorySize, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
loop(Host, ServerHost, Access)
end.
ok
end,
{noreply, State};
handle_info({room_destroyed, RoomHost}, State) ->
ets:delete(muc_online_room, RoomHost),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
do_route(Host, ServerHost, Access, From, To, Packet) ->
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
start_supervisor(Host) ->
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
ChildSpec =
{Proc,
{ejabberd_tmp_sup, start_link,
[Proc, mod_muc_room]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop_supervisor(Host) ->
Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
do_route(Host, ServerHost, Access, HistorySize, From, To, Packet) ->
{AccessRoute, _AccessCreate, _AccessAdmin} = Access,
case acl:match_rule(ServerHost, AccessRoute, From) of
allow ->
do_route1(Host, ServerHost, Access, From, To, Packet);
do_route1(Host, ServerHost, Access, HistorySize, From, To, Packet);
_ ->
{xmlelement, _Name, Attrs, _Els} = Packet,
Lang = xml:get_attr_s("xml:lang", Attrs),
@@ -95,7 +258,7 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
end.
do_route1(Host, ServerHost, Access, From, To, Packet) ->
do_route1(Host, ServerHost, Access, HistorySize, From, To, Packet) ->
{_AccessRoute, AccessCreate, AccessAdmin} = Access,
{Room, _, Nick} = jlib:jid_tolower(To),
{xmlelement, Name, Attrs, _Els} = Packet,
@@ -107,7 +270,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) ->
"iq" ->
case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
sub_el = SubEl} = IQ ->
sub_el = _SubEl} = IQ ->
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", XMLNS}],
@@ -123,7 +286,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) ->
#iq{type = get,
xmlns = ?NS_REGISTER = XMLNS,
lang = Lang,
sub_el = SubEl} = IQ ->
sub_el = _SubEl} = IQ ->
Res = IQ#iq{type = result,
sub_el =
[{xmlelement, "query",
@@ -155,7 +318,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) ->
#iq{type = get,
xmlns = ?NS_VCARD = XMLNS,
lang = Lang,
sub_el = SubEl} = IQ ->
sub_el = _SubEl} = IQ ->
Res = IQ#iq{type = result,
sub_el =
[{xmlelement, "vCard",
@@ -220,7 +383,8 @@ do_route1(Host, ServerHost, Access, From, To, Packet) ->
?DEBUG("MUC: open new room '~s'~n", [Room]),
{ok, Pid} = mod_muc_room:start(
Host, ServerHost, Access,
Room, From, Nick),
Room, HistorySize, From,
Nick),
ets:insert(
muc_online_room,
#muc_online_room{name_host = {Room, Host},
@@ -252,41 +416,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) ->
room_destroyed(Host, Room, ServerHost) ->
gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
{room_destroyed, {Room, Host}},
ok.
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
store_room(Host, Name, Opts) ->
F = fun() ->
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
mnesia:transaction(F).
restore_room(Host, Name) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
[#muc_room{opts = Opts}] ->
Opts;
_ ->
error
end.
forget_room(Host, Name) ->
F = fun() ->
mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F).
load_permanent_rooms(Host, ServerHost, Access) ->
load_permanent_rooms(Host, ServerHost, Access, HistorySize) ->
case catch mnesia:dirty_select(
muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'},
[],
@@ -302,6 +432,7 @@ load_permanent_rooms(Host, ServerHost, Access) ->
ServerHost,
Access,
Room,
HistorySize,
R#muc_room.opts),
ets:insert(
muc_online_room,
@@ -315,21 +446,12 @@ iq_disco_info() ->
[{xmlelement, "identity",
[{"category", "conference"},
{"type", "text"},
{"name", "ejabberd/mod_muc"}], []},
{"name", "Chatrooms"}], []},
{xmlelement, "feature", [{"var", ?NS_MUC}], []},
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS}],
iq_disco_items(Host, From, Lang)}]},
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res)).
iq_disco_items(Host, From, Lang) ->
lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(
@@ -380,76 +502,81 @@ iq_get_register_info(Host, From, Lang) ->
Lang, "Enter nickname you want to register")}]},
?XFIELD("text-single", "Nickname", "nick", Nick)]}].
iq_set_register_info(Host, From, XData, Lang) ->
iq_set_register_info(Host, From, Nick, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
case lists:keysearch("nick", 1, XData) of
false ->
ErrText = "You must fill in field \"nick\" in the form",
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
{value, {_, [Nick]}} ->
F = fun() ->
case Nick of
"" ->
mnesia:delete({muc_registered, {LUS, Host}}),
F = fun() ->
case Nick of
"" ->
mnesia:delete({muc_registered, {LUS, Host}}),
ok;
_ ->
Allow =
case mnesia:select(
muc_registered,
[{#muc_registered{us_host = '$1',
nick = Nick,
_ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]) of
[] ->
true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end,
if
Allow ->
mnesia:write(
#muc_registered{us_host = {LUS, Host},
nick = Nick}),
ok;
_ ->
Allow =
case mnesia:select(
muc_registered,
[{#muc_registered{us_host = '$1',
nick = Nick,
_ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]) of
[] ->
true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end,
if
Allow ->
mnesia:write(
#muc_registered{us_host = {LUS, Host},
nick = Nick}),
ok;
true ->
false
end
true ->
false
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
{result, []};
{atomic, false} ->
ErrText = "Specified nickname is already registered",
{error, ?ERRT_CONFLICT(Lang, ErrText)};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
{result, []};
{atomic, false} ->
ErrText = "Specified nickname is already registered",
{error, ?ERRT_CONFLICT(Lang, ErrText)};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
process_iq_register_set(Host, From, SubEl, Lang) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
case {xml:get_tag_attr_s("xmlns", XEl),
xml:get_tag_attr_s("type", XEl)} of
{?NS_XDATA, "cancel"} ->
{result, []};
{?NS_XDATA, "submit"} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
invalid ->
{error, ?ERR_BAD_REQUEST};
case xml:get_subtag(SubEl, "remove") of
false ->
case xml:remove_cdata(Els) of
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
case {xml:get_tag_attr_s("xmlns", XEl),
xml:get_tag_attr_s("type", XEl)} of
{?NS_XDATA, "cancel"} ->
{result, []};
{?NS_XDATA, "submit"} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
invalid ->
{error, ?ERR_BAD_REQUEST};
_ ->
case lists:keysearch("nick", 1, XData) of
false ->
ErrText = "You must fill in field \"Nickname\" in the form",
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
{value, {_, [Nick]}} ->
iq_set_register_info(Host, From, Nick, Lang)
end
end;
_ ->
iq_set_register_info(Host, From, XData, Lang)
{error, ?ERR_BAD_REQUEST}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
iq_set_register_info(Host, From, "", Lang)
end.
iq_get_vcard(Lang) ->
@@ -460,7 +587,7 @@ iq_get_vcard(Lang) ->
"http://ejabberd.jabberstudio.org/"}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(Lang, "ejabberd MUC module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
broadcast_service_message(Host, Msg) ->
@@ -477,26 +604,6 @@ get_vh_rooms(Host) ->
['$_']}]).
can_use_nick(_Host, _JID, "") ->
false;
can_use_nick(Host, JID, Nick) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
case catch mnesia:dirty_select(
muc_registered,
[{#muc_registered{us_host = '$1',
nick = Nick,
_ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]) of
{'EXIT', _Reason} ->
true;
[] ->
true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
end.
update_tables(Host) ->
update_muc_room_table(Host),
+749
View File
@@ -0,0 +1,749 @@
%%%----------------------------------------------------------------------
%%% File : mod_muc_log.erl
%%% Author : Badlop
%%% Purpose : MUC room logging
%%% Created : 12 Mar 2006 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_muc_log).
-author('badlop').
-behaviour(gen_server).
-behaviour(gen_mod).
%% API
-export([start_link/2,
start/2,
stop/1,
check_access_log/2,
add_to_log/5]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(T(Text), translate:translate(Lang, Text)).
-define(PROCNAME, ejabberd_mod_muc_log).
-record(room, {jid, title, subject, subject_author, config}).
-record(state, {host,
out_dir,
dir_type,
css_file,
access,
lang,
timezone,
top_link}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc).
add_to_log(Host, Type, Data, Room, Opts) ->
gen_server:cast(get_proc_name(Host),
{add_to_log, Type, Data, Room, Opts}).
check_access_log(Host, From) ->
case catch gen_server:call(get_proc_name(Host),
{check_access_log, Host, From}) of
{'EXIT', _Error} ->
false;
Res ->
Res
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"),
DirType = gen_mod:get_opt(dirtype, Opts, subdirs),
CSSFile = gen_mod:get_opt(cssfile, Opts, false),
AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin),
Timezone = gen_mod:get_opt(timezone, Opts, local),
Top_link = gen_mod:get_opt(top_link, Opts, {"/", "Home"}),
Lang = case ejabberd_config:get_local_option({language, Host}) of
undefined ->
"";
L ->
L
end,
{ok, #state{host = Host,
out_dir = OutDir,
dir_type = DirType,
css_file = CSSFile,
access = AccessLog,
lang = Lang,
timezone = Timezone,
top_link = Top_link}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({check_access_log, ServerHost, FromJID}, _From, State) ->
Reply = acl:match_rule(ServerHost, State#state.access, FromJID),
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({add_to_log, Type, Data, Room, Opts}, State) ->
case catch add_to_log2(Type, Data, Room, Opts, State) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
{false, false} ->
ok;
{false, SubEl} ->
Message = {body, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
Message = {subject, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
add_to_log2(roomconfig_change, _, Room, Opts, State) ->
add_message_to_log("", roomconfig_change, Room, Opts, State);
add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) ->
add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State);
add_to_log2(join, Nick, Room, Opts, State) ->
add_message_to_log(Nick, join, Room, Opts, State);
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) ->
case Reason of
"" -> add_message_to_log(Nick, leave, Room, Opts, State);
_ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State)
end;
add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) ->
add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State).
%%----------------------------------------------------------------------
%% Core
build_filename_string(TimeStamp, OutDir, RoomJID, DirType) ->
{{Year, Month, Day}, _Time} = TimeStamp,
% Directory and file names
{Dir, Filename, Rel} =
case DirType of
subdirs ->
SYear = lists:flatten(io_lib:format("~4..0w", [Year])),
SMonth = lists:flatten(io_lib:format("~2..0w", [Month])),
SDay = lists:flatten(io_lib:format("~2..0w", [Day])),
{filename:join(SYear, SMonth), SDay, "../.."};
plain ->
Date = lists:flatten(
io_lib:format("~4..0w-~2..0w-~2..0w",
[Year, Month, Day])),
{"", Date, "."}
end,
Fd = filename:join([OutDir, RoomJID, Dir]),
Fn = filename:join([Fd, Filename ++ ".html"]),
Fnrel = filename:join([Rel, Dir, Filename ++ ".html"]),
{Fd, Fn, Fnrel}.
% calculate day before
get_timestamp_daydiff(TimeStamp, Daydiff) ->
{Date1, HMS} = TimeStamp,
Date2 = calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(Date1) + Daydiff),
{Date2, HMS}.
% Try to close the previous day log, if it exists
close_previous_log(Fn, Images_dir) ->
case file:read_file_info(Fn) of
{ok, _} ->
{ok, F} = file:open(Fn, [append]),
%fw(F, "<div class=\"legend\">ejabberd/mod_muc log<span class=\"w3c\">"),
fw(F, "<div class=\"legend\">"),
fw(F, " <a href=\"http://ejabberd.jabber.ru\"><img style=\"border:0\" src=\"~s/powered-by-ejabberd.png\" alt=\"Powered by ejabberd\"/></a>", [Images_dir]),
fw(F, " <a href=\"http://www.erlang.org/\"><img style=\"border:0\" src=\"~s/powered-by-erlang.png\" alt=\"Powered by Erlang\"/></a>", [Images_dir]),
fw(F, "<span class=\"w3c\">"),
fw(F, " <a href=\"http://validator.w3.org/check?uri=referer\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/valid-xhtml10.png\" alt=\"Valid XHTML 1.0 Transitional\" /></a>", [Images_dir]),
fw(F, " <a href=\"http://jigsaw.w3.org/css-validator/\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/vcss.png\" alt=\"Valid CSS!\"/></a>", [Images_dir]),
fw(F, "</span></div></body></html>"),
file:close(F);
_ -> ok
end.
add_message_to_log(Nick1, Message, RoomJID, Opts, State) ->
Nick = htmlize(Nick1),
#state{out_dir = OutDir,
dir_type = DirType,
css_file = CSSFile,
lang = Lang,
timezone = Timezone,
top_link = TopLink} = State,
Room = get_room_info(RoomJID, Opts),
TimeStamp = case Timezone of
local -> calendar:now_to_local_time(now());
universal -> calendar:now_to_universal_time(now())
end,
{Fd, Fn, _Dir} = build_filename_string(TimeStamp, OutDir, Room#room.jid, DirType),
{Date, Time} = TimeStamp,
% Open file, create if it does not exist, create parent dirs if needed
case file:read_file_info(Fn) of
{ok, _} ->
{ok, F} = file:open(Fn, [append]);
{error, enoent} ->
make_dir_rec(Fd),
{ok, F} = file:open(Fn, [append]),
Datestring = get_dateweek(Date, Lang),
TimeStampYesterday = get_timestamp_daydiff(TimeStamp, -1),
{_FdYesterday, FnYesterday, DatePrev} =
build_filename_string(
TimeStampYesterday, OutDir, Room#room.jid, DirType),
TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
{_FdTomorrow, _FnTomorrow, DateNext} =
build_filename_string(
TimeStampTomorrow, OutDir, Room#room.jid, DirType),
HourOffset = calc_hour_offset(TimeStamp),
put_header(F, Room, Datestring, CSSFile, Lang,
HourOffset, DatePrev, DateNext, TopLink),
Images_dir = filename:join([OutDir, "images"]),
file:make_dir(Images_dir),
create_image_files(Images_dir),
Images_url = case DirType of
subdirs -> "../../../images";
plain -> "../images"
end,
close_previous_log(FnYesterday, Images_url)
end,
% Build message
Text = case Message of
roomconfig_change ->
RoomConfig = roomconfig_to_string(Room#room.config, Lang),
put_room_config(F, RoomConfig, Lang),
io_lib:format("<font class=\"mrcm\">~s</font><br/>",
[?T("Chatroom configuration modified")]);
join ->
io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
[Nick, ?T("joins the room")]);
leave ->
io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
[Nick, ?T("leaves the room")]);
{leave, Reason} ->
io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
[Nick, ?T("leaves the room"), htmlize(Reason)]);
{kickban, "307", ""} ->
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
[Nick, ?T("has been kicked")]);
{kickban, "307", Reason} ->
io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
[Nick, ?T("has been kicked"), htmlize(Reason)]);
{kickban, "301", ""} ->
io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
[Nick, ?T("has been banned")]);
{kickban, "301", Reason} ->
io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
[Nick, ?T("has been banned"), htmlize(Reason)]);
{nickchange, OldNick} ->
io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
[OldNick, ?T("is now known as"), Nick]);
{subject, T} ->
io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
[Nick, ?T(" has set the subject to: "), htmlize(T)]);
{body, T} ->
case regexp:first_match(T, "^/me\s") of
{match, _, _} ->
io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
[Nick, string:substr(htmlize(T), 5)]);
nomatch ->
io_lib:format("<font class=\"mn\">&lt;~s&gt;</font> ~s<br/>",
[Nick, htmlize(T)])
end
end,
{Hour, Minute, Second} = Time,
STime = lists:flatten(
io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Minute, Second])),
% Write message
file:write(F, io_lib:format("<a name=\"~s\" href=\"#~s\" class=\"ts\">[~s]</a> ~s~n",
[STime, STime, STime, Text])),
% Close file
file:close(F),
ok.
%%----------------------------------------------------------------------
%% Utilities
get_dateweek(Date, Lang) ->
Weekday = case calendar:day_of_the_week(Date) of
1 -> ?T("Monday");
2 -> ?T("Tuesday");
3 -> ?T("Wednesday");
4 -> ?T("Thursday");
5 -> ?T("Friday");
6 -> ?T("Saturday");
7 -> ?T("Sunday")
end,
{Y, M, D} = Date,
Month = case M of
1 -> ?T("January");
2 -> ?T("February");
3 -> ?T("March");
4 -> ?T("April");
5 -> ?T("May");
6 -> ?T("June");
7 -> ?T("July");
8 -> ?T("August");
9 -> ?T("September");
10 -> ?T("October");
11 -> ?T("November");
12 -> ?T("December")
end,
case Lang of
"en" -> io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
"es" -> io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]);
_ -> io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
end.
make_dir_rec(Dir) ->
case file:read_file_info(Dir) of
{ok, _} ->
ok;
{error, enoent} ->
DirS = filename:split(Dir),
DirR = lists:sublist(DirS, length(DirS)-1),
make_dir_rec(filename:join(DirR)),
file:make_dir(Dir)
end.
% {ok, F1}=file:open("valid-xhtml10.png", [read]).
% {ok, F1b}=file:read(F1, 1000000).
% c("../../ejabberd/src/jlib.erl").
% jlib:encode_base64(F1b).
image_base64("powered-by-erlang.png") ->
"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoAAADN0lEQVRo3u1a"
"P0waURz+rjGRRQ+nUyRCYmJyDPTapDARaSIbTUjt1gVSh8ZW69aBAR0cWLSx"
"CXWp59LR1jbdqKnGxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu"
"33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOHo8HkiQxDBXEOjg9"
"PcHc3BxuUSqsI8jR0REAUFGsCCoKFYWCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJ"
"ut9hMQlvdNbUhKWshLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm"
"pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1MdG8rSKKXlJ7lHr"
"fjouCut2IrC82BDPbe/gc+xlXez7KxEz63H4lmIN473Rh8Si1BKhRY6aEJI8"
"pLmbjSPN0xOnBBILmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG"
"2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFOTYznzpPO+IQi+IK"
"+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN3Olvk4ge/IU98spne+FfGtlslm16nn"
"a8fduntfDscoVjGJqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5"
"ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizgwwu5SLK8jllu1t9"
"ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y1m+hSj13WjqOiJKK0qpXKhSFxJAYBv"
"KYaZ9TjWRu4SiWi2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g"
"icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJvHsdL+utNYrm2uf"
"ZDVZSkzPKaQkW8kthpyS297BvRdRzR6DdTurJbPy9Ov1K6xr3HBPQuIMowR3"
"asegUyDuU9SuUG+dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q"
"lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafIYrizbfESoB+dd9/"
"KF/d/wX3cJvREzl1vAAAAABJRU5ErkJggg==";
image_base64("valid-xhtml10.png") ->
"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEAAACiFBMVEUAAADe"
"5+fOezmtra3ejEKlhELvvWO9WlrehELOe3vepaWclHvetVLGc3PerVKcCAj3"
"vVqUjHOUe1JjlL0xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY"
"WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoICACEazEhGAgIAACE"
"YzFra2utjELWcznGnEr/7+9jY2POazHOYzGta2NShLVrlL05OUqctdacCADG"
"a2ucAADGpVqUtc61ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe"
"3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7/fGxsa9pVqEOSkp"
"Y5xznL29tZxahLXOpVr/99ZrY1L/79ZjUiljSikAOYTvxmMAMYScezmchFqU"
"czGtlFp7c2utjFqUlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC"
"Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1mtCMRhzY1L/zmvn"
"vVpSQiHOpVJrUinntVr3zmOEc1L3xmNaWlq1nFo5QkrGWim1lFoISpRSUlK1"
"zt4hWpwASoz///////8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3"
"9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUlHtKORh7a1IxIRCU"
"jHtaSiHWrVIpIQhzWinvvVpaQiH/1mPWpVKMe1L/zmP/xmNrUiGErc4YGBj/"
"73PG1ucQWpT/53O9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS"
"SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////iP9sSf//dP//////"
"//////////////////////////////////////////8M////////////ef//"
"////////////////////////////////////////////////////////////"
"//////////////////////9d////////////////////////////////////"
"AP//////////////CP//RP//////////////////////////////////////"
"//////////////////////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba"
"iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpIvGQGzokvTTA85VH"
"KTpbRoeJnPno/p1+M7t3txj20e/Nzu7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSA"
"NeBDxVmxZZSwEUYkGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb"
"Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6VbdtBblesOs+MjmEmz"
"JKNIJWFEfEQTCWNPFKvcKEymjLO1b8bwYQd1hCiiDCl5KsrDCIlhj4fSuvcp"
"fSpgJmyv6dzeZv+nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk"
"WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDNE9uzA1YD/5MhnzW"
"1KRqF7mYG8jFYXLcfLpjOe2LA0fuGqQrQHl10sdK0sFcFSOSlzF0BgXQH9h3"
"QZDBI0ccNEhftjXuippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7"
"eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woWSRJ0m2ewhvAOUiY"
"YtZtaZL0CZZmtmVOQttLfr/dbveLZodrfrL7W75wG/JjqkQxoNTtNsTKELQp"
"QL6/D5loaSmyTT8TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+"
"AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qqmj5bHqVZlJKZIRk"
"1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p1V76DeIs0MX+s3GxRlA1vtw83KhgNp"
"hc1nyErLO5zcvbOsrq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N"
"E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4Fsvjmuv662jsWxPZ"
"x2xg890bYMYnTgya7bjmCiEY0qgJ0vMF3c+NoFdPyzxz6V3Uxs3AOWCDchRv"
"OsQtBrbFsrT2fhHEc7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1"
"7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3e4HFmYPDl/thYcR"
"Vi8qPEoXVUWBl6FTBFTtnqmKKg5wnlF4wZ1yeLv7TiwXKektE+iDBNicWEyL"
"pnFhfDkpJc3q2khSPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx"
"Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdiMOlEOJHw1jOQ4sW"
"t5rQopRkXZhMEi7pmeDCVWBlfUKwhMZ7rsF6elKsvbwiKxgxIdewa3ErsaYo"
"mCVZFYJb0GUu3JqGUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+"
"rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx17XhBuDCaOerbt5"
"38erta7TFktLvdHghZcCbcPQO33zIJG9kxF5hoVXnzTzRz0r5js8oTj6uyPk"
"GRf346HOLcasgFexueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl"
"0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1juvBSWNFmNWGZy8Lv"
"zUl5PN4JCwyNDzbQ0aAj4Zrjz0FatGJJYhvq4j7mGSpvytGFlZtHf2C4o/28"
"Zu8z7wo7eYPfXysnF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO"
"DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+faCSJPJ1bmy6Thyvp"
"ivBmYWgjxPDPAp7JTemY/yGKFEiRt/jG/2P79s8KCwoLCgoLC/khUBA5F0Sf"
"QZ+RYfpNE/4Xosmq7jsZAJsAAAAASUVORK5CYII=";
image_base64("vcss.png") ->
"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSAAABKVBMVEUAAAAj"
"Ix8MR51ZVUqAdlmdnZ3ejEWLDAuNjY1kiMG0n2d9fX19Ghfrp1FtbW3y39+3"
"Ph6lIRNdXV2qJBFcVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd"
"GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T09RRDwsJBG+vr73"
"wV6fkG6eCQRFcLSurq6/X1+ht9nXfz5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5t"
"WCsmWajsz8+Sq9NMPh4hVaY8MRj///////////////////////9MTEyOp9Lu"
"8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn82dfz7/zGafDw+f"
"Dw+zRSlzlMcMDAyNcji1tbXf5vIcFgvATJOjAAAAY3RSTlP/8///////////"
"//////8A//////P/////ov//8//////////////z///T//////////+i////"
"//////////8w/////6IA/xAgMP//////////8/////////8w0/////////+z"
"ehebAAACkUlEQVR42u2VfVPTQBDG19VqC6LY+lKrRIxFQaFSBPuSvhBPF8SI"
"UZK2J5Yav/+HcO8uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6"
"9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE24pFtBtzG1Q4AoD5"
"zvw5pEDcJvIQV/TE3/l+H9GnNJwcdABS5wAbFQLMqI98/UReoAaOTlaJsp0z"
"aHx7LwZvY0BUR2xpWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7"
"34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45OeuPgm4+Xjtv7xm4N"
"7PMV4C61+Mrz3H2WImm3ATiWrAiwZRWcUA5Ej4dgIEMxDv6yxHHcNuAutnjv"
"2HZ1NeuycoVPh0mwC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW"
"KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyGqidQSw1eqtJcA8c"
"Zq4zXqhPuCBYE1fKJjh/5X6MwRm9c2xf7WVdLf5oSdt64esVIwVAKC1HJ2ol"
"i8vj3L0YzC4zjkMagt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD"
"VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz54uUyet+hBM7BXX"
"DS8Xc5+8Gmmbu1xwSoGIokA3oTptQecQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo"
"+W9xC8oWpugVCXLuwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua"
"ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/AV5htgaQ9SeRAAAA"
"AElFTkSuQmCC";
image_base64("powered-by-ejabberd.png") ->
"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaAAAAw1BMVEUAAAAj"
"BgYtBAM5AwFCAAAYGAJNAABcAABIDQ5qAAAoJRV7AACFAAAoKSdJHByLAAAw"
"Lwk1NQA1MzFJKyo4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb"
"W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEgAB3dnd4d2+OjACD"
"hYKcmACJi4iQkpWspgCYmJm5swCmqazEwACwsbS4ub3X0QLExsPLyszW1Nnc"
"3ODm5ugMBwAWAwPHm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ"
"cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VBkCMAAACcElEQVRI"
"x72WjXKiMBSFQalIFbNiy1pdrJZaRVYR5deGwPs/VRNBSBB2OjvQO0oYjPfj"
"5J6bCcdx8i2UldxKcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv"
"1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJlRpsUei0JF6Qi+O"
"HyGK7ijXxPklHe/umIllim3iUBMJDIEULxxPP0TVWhhKJoN9fUpdmQLteV8a"
"DgEAg9gIcTjL4F4L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF"
"FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7rovDjMpIHHGYeid"
"Xn4TQcaxMYqP3RV3C8oCl2WvrlSPaNpGZadRnmPGCk8ylM2okAJ4i9TEe1Ke"
"rsXxSl6jUt5uayiIodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs"
"VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzvBo1I2tLmuL69kSi"
"tAweyHKN9b3leDfQMnu3nIIKWfmXnqGVKedJT6QpICbJvf2f8aOsvn68v+k7"
"/cwUQdPoxaMoRTnKFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG"
"Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCTLav3VrqbPvP3vdd"
"uGEhQJzilncbpSA4F3vsihErO+dayv/sY5/yRE0GDEXCu2VoNiMlo5i+P2Kl"
"gMEvTNk2eYa5XEyh12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA"
"AElFTkSuQmCC".
create_image_files(Images_dir) ->
Filenames = [
"powered-by-ejabberd.png",
"powered-by-erlang.png",
"valid-xhtml10.png",
"vcss.png"
],
lists:foreach(
fun(Filename) ->
Filename_full = filename:join([Images_dir, Filename]),
{ok, F} = file:open(Filename_full, [write]),
Image = jlib:decode_base64(image_base64(Filename)),
io:format(F, "~s", [Image]),
file:close(F)
end,
Filenames),
ok.
fw(F, S, O) -> io:format(F, S ++ "~n", O).
fw(F, S) -> fw(F, S, []).
put_header(F, Room, Date, CSSFile, Lang, Hour_offset, Date_prev, Date_next, Top_link) ->
fw(F, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"),
fw(F, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"~s\" lang=\"~s\">", [Lang, Lang]),
fw(F, "<head>"),
fw(F, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"),
fw(F, "<title>~s - ~s</title>", [htmlize(Room#room.title), Date]),
put_header_css(F, CSSFile),
put_header_script(F),
fw(F, "</head>"),
fw(F, "<body>"),
{Top_url, Top_text} = Top_link,
fw(F, "<div style=\"text-align: right;\"><a style=\"color: #AAAAAA; font-family: monospace; text-decoration: none; font-weight: bold;\" href=\"~s\">~s</a></div>", [Top_url, Top_text]),
fw(F, "<div class=\"roomtitle\">~s</div>", [htmlize(Room#room.title)]),
fw(F, "<a class=\"roomjid\" href=\"xmpp:~s?join\">~s</a>", [Room#room.jid, Room#room.jid]),
fw(F, "<div class=\"logdate\">~s<span class=\"w3c\"><a class=\"nav\" href=\"~s\">&lt;</a> <a class=\"nav\" href=\".\/\">^</a> <a class=\"nav\" href=\"~s\">&gt;</a></span></div>", [Date, Date_prev, Date_next]),
case {htmlize(Room#room.subject_author), htmlize(Room#room.subject)} of
{"", ""} -> ok;
{SuA, Su} -> fw(F, "<div class=\"roomsubject\">~s~s~s</div>", [SuA, ?T(" has set the subject to: "), Su])
end,
RoomConfig = roomconfig_to_string(Room#room.config, Lang),
put_room_config(F, RoomConfig, Lang),
Time_offset_str = case Hour_offset<0 of
true -> io_lib:format("~p", [Hour_offset]);
false -> io_lib:format("+~p", [Hour_offset])
end,
fw(F, "<br/><a class=\"ts\">GMT~s</a><br/>", [Time_offset_str]).
put_header_css(F, false) ->
fw(F, "<style type=\"text/css\">"),
fw(F, "<!--"),
fw(F, ".ts {color: #AAAAAA; text-decoration: none;}"),
fw(F, ".mrcm {color: #009900; font-style: italic; font-weight: bold;}"),
fw(F, ".msc {color: #009900; font-style: italic; font-weight: bold;}"),
fw(F, ".mj {color: #009900; font-style: italic;}"),
fw(F, ".ml {color: #009900; font-style: italic;}"),
fw(F, ".mk {color: #009900; font-style: italic;}"),
fw(F, ".mb {color: #009900; font-style: italic;}"),
fw(F, ".mnc {color: #009900; font-style: italic;}"),
fw(F, ".mn {color: #0000AA;}"),
fw(F, ".mne {color: #AA0099;}"),
fw(F, "a.nav {color: #AAAAAA; font-family: monospace; letter-spacing: 3px; text-decoration: none;}"),
fw(F, "div.roomtitle {border-bottom: #224466 solid 3pt; margin-left: 20pt;}"),
fw(F, "div.roomtitle {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; text-decoration: none;}"),
fw(F, "a.roomjid {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; margin-left: 20pt; text-decoration: none;}"),
fw(F, "div.logdate {color: #663399; font-size: 20px; font-weight: bold; font-family: sans-serif; letter-spacing: 2px; border-bottom: #224466 solid 1pt; margin-left:80pt; margin-top:20px;}"),
fw(F, "div.roomsubject {color: #336699; font-size: 18px; font-family: sans-serif; margin-left: 80pt; margin-bottom: 10px;}"),
fw(F, "div.rc {color: #336699; font-size: 12px; font-family: sans-serif; margin-left: 50%; text-align: right; background: #f3f6f9; border-bottom: 1px solid #336699; border-right: 4px solid #336699;}"),
fw(F, "div.rct {font-weight: bold; background: #e3e6e9; padding-right: 10px;}"),
fw(F, "div.rcos {padding-right: 10px;}"),
fw(F, "div.rcoe {color: green;}"),
fw(F, "div.rcod {color: red;}"),
fw(F, "div.rcoe:after {content: \": v\";}"),
fw(F, "div.rcod:after {content: \": x\";}"),
fw(F, "div.rcot:after {}"),
fw(F, ".legend {width: 100%; margin-top: 30px; border-top: #224466 solid 1pt; padding: 10px 0px 10px 0px; text-align: left; font-family: monospace; letter-spacing: 2px;}"),
fw(F, ".w3c {position: absolute; right: 10px; width: 60%; text-align: right; font-family: monospace; letter-spacing: 1px;}"),
fw(F, "//-->"),
fw(F, "</style>");
put_header_css(F, CSSFile) ->
fw(F, "<link rel=\"stylesheet\" type=\"text/css\" href=\"~s\" media=\"all\">", [CSSFile]).
put_header_script(F) ->
fw(F, "<script type=\"text/javascript\">"),
fw(F, "function sh(e) // Show/Hide an element"),
fw(F, "{if(document.getElementById(e).style.display=='none')"),
fw(F, "{document.getElementById(e).style.display='block';}"),
fw(F, "else {document.getElementById(e).style.display='none';}}"),
fw(F, "</script>").
put_room_config(F, RoomConfig, Lang) ->
{_, Now2, _} = now(),
fw(F, "<div class=\"rc\">"),
fw(F, "<div class=\"rct\" onclick=\"sh('a~p');return false;\">~s</div>", [Now2, ?T("Room Configuration")]),
fw(F, "<div class=\"rcos\" id=\"a~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomConfig]),
fw(F, "</div>").
htmlize(S1) ->
S2_list = string:tokens(S1, "\n"),
lists:foldl(
fun(Si, Res) ->
Si2 = htmlize2(Si),
case Res of
"" -> Si2;
_ -> Res ++ "<br/>" ++ Si2
end
end,
"",
S2_list).
htmlize2(S1) ->
S2 = element(2, regexp:gsub(S1, "\\&", "\\&amp;")),
S3 = element(2, regexp:gsub(S2, "<", "\\&lt;")),
S4 = element(2, regexp:gsub(S3, ">", "\\&gt;")),
S5 = element(2, regexp:gsub(S4, "(http|ftp)://.[^ ]*", "<a href=\"&\">&</a>")),
S5.
get_room_info(RoomJID, Opts) ->
Title =
case lists:keysearch(title, 1, Opts) of
{value, {_, T}} -> T;
false -> ""
end,
Subject =
case lists:keysearch(subject, 1, Opts) of
{value, {_, S}} -> S;
false -> ""
end,
SubjectAuthor =
case lists:keysearch(subject_author, 1, Opts) of
{value, {_, SA}} -> SA;
false -> ""
end,
#room{jid = jlib:jid_to_string(RoomJID),
title = Title,
subject = Subject,
subject_author = SubjectAuthor,
config = Opts
}.
roomconfig_to_string(Options, Lang) ->
% Get title, if available
Title = case lists:keysearch(title, 1, Options) of
{value, Tuple} -> [Tuple];
false -> []
end,
% Remove title from list
Os1 = lists:keydelete(title, 1, Options),
% Order list
Os2 = lists:sort(Os1),
% Add title to ordered list
Options2 = Title ++ Os2,
lists:foldl(
fun({Opt, Val}, R) ->
case get_roomconfig_text(Opt) of
undefined ->
R;
OptT ->
OptText = ?T(OptT),
R2 = case Val of
false -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
true -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
"" -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
T ->
case Opt of
password -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
title -> "<div class=\"rcot\">" ++ ?T("Room title") ++ ": \"" ++ htmlize(T) ++ "\"</div>";
_ -> "\"" ++ T ++ "\""
end
end,
R ++ R2
end
end,
"",
Options2).
get_roomconfig_text(title) -> "Room title";
get_roomconfig_text(persistent) -> "Make room persistent";
get_roomconfig_text(public) -> "Make room public searchable";
get_roomconfig_text(public_list) -> "Make participants list public";
get_roomconfig_text(password_protected) -> "Make room password protected";
get_roomconfig_text(password) -> "Password";
get_roomconfig_text(anonymous) -> "Make room semianonymous";
get_roomconfig_text(members_only) -> "Make room members-only";
get_roomconfig_text(moderated) -> "Make room moderated";
get_roomconfig_text(members_by_default) -> "Default users as participants";
get_roomconfig_text(allow_change_subj) -> "Allow users to change subject";
get_roomconfig_text(allow_private_messages) -> "Allow users to send private messages";
get_roomconfig_text(allow_query_users) -> "Allow users to query other users";
get_roomconfig_text(allow_user_invites) -> "Allow users to send invites";
get_roomconfig_text(logging) -> "Enable logging";
get_roomconfig_text(_) -> undefined.
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
calc_hour_offset(TimeHere) ->
TimeZero = calendar:now_to_universal_time(now()),
TimeHereHour = calendar:datetime_to_gregorian_seconds(TimeHere) div 3600,
TimeZeroHour = calendar:datetime_to_gregorian_seconds(TimeZero) div 3600,
TimeHereHour - TimeZeroHour.
+370 -178
View File
@@ -14,8 +14,10 @@
%% External exports
-export([start/6,
start/5,
-export([start_link/7,
start_link/6,
start/7,
start/6,
route/4]).
%% gen_fsm callbacks
@@ -49,7 +51,7 @@
password_protected = false,
password = "",
anonymous = true,
logging = false % TODO
logging = false
}).
-record(user, {jid,
@@ -83,12 +85,23 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, Creator, Nick) ->
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, Creator, Nick],
?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, Creator, Nick) ->
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
supervisor:start_child(
Supervisor, [Host, ServerHost, Access, Room, HistorySize, Creator, Nick]).
start(Host, ServerHost, Access, Room, Opts) ->
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, Opts], ?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, Opts) ->
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
supervisor:start_child(
Supervisor, [Host, ServerHost, Access, Room, HistorySize, Opts]).
start_link(Host, ServerHost, Access, Room, HistorySize, Creator, Nick) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, Creator, Nick],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, Opts) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, Opts],
?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -101,20 +114,22 @@ start(Host, ServerHost, Access, Room, Opts) ->
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([Host, ServerHost, Access, Room, Creator, Nick]) ->
init([Host, ServerHost, Access, Room, HistorySize, Creator, _Nick]) ->
State = set_affiliation(Creator, owner,
#state{host = Host,
server_host = ServerHost,
access = Access,
room = Room,
history = lqueue_new(HistorySize),
jid = jlib:make_jid(Room, Host, ""),
just_created = true}),
{ok, normal_state, State};
init([Host, ServerHost, Access, Room, Opts]) ->
init([Host, ServerHost, Access, Room, HistorySize, Opts]) ->
State = set_opts(Opts, #state{host = Host,
server_host = ServerHost,
access = Access,
room = Room,
history = lqueue_new(HistorySize),
jid = jlib:make_jid(Room, Host, "")}),
{ok, normal_state, State}.
@@ -357,7 +372,11 @@ normal_state({route, From, Nick,
NewState =
add_user_presence_un(From, Packet, StateData),
send_new_presence(From, NewState),
remove_online_user(From, NewState);
Reason = case xml:get_subtag(Packet, "status") of
false -> "";
Status_el -> xml:get_tag_cdata(Status_el)
end,
remove_online_user(From, NewState, Reason);
_ ->
StateData
end;
@@ -559,16 +578,11 @@ normal_state({route, From, ToNick,
end,
{next_state, normal_state, StateData};
normal_state(Event, StateData) ->
normal_state(_Event, StateData) ->
{next_state, normal_state, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
@@ -848,10 +862,17 @@ add_online_user(JID, Nick, Role, StateData) ->
nick = Nick,
role = Role},
StateData#state.users),
add_to_log(join, Nick, StateData),
StateData#state{users = Users}.
remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, "").
remove_online_user(JID, StateData, Reason) ->
LJID = jlib:jid_tolower(JID),
{ok, #user{nick = Nick}} =
?DICT:find(LJID, StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
Users = ?DICT:erase(LJID, StateData#state.users),
StateData#state{users = Users}.
@@ -862,15 +883,13 @@ filter_presence({xmlelement, "presence", Attrs, Els}) ->
case El of
{xmlcdata, _} ->
false;
{xmlelement, Name1, Attrs1, _Els1} ->
{xmlelement, _Name1, Attrs1, _Els1} ->
XMLNS = xml:get_attr_s("xmlns", Attrs1),
case {Name1, XMLNS} of
{"show", ""} ->
true;
{"status", ""} ->
true;
case XMLNS of
?NS_MUC ++ _ ->
false;
_ ->
false
true
end
end
end, Els),
@@ -971,8 +990,22 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
add_user_presence(
From, Packet,
add_online_user(From, Nick, Role, StateData)),
send_new_presence(From, NewState),
if not (NewState#state.config)#config.anonymous ->
WPacket = {xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [],
[{xmlcdata, translate:translate(
Lang,
"This room is not anonymous")}]},
{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
[{xmlelement, "status", [{"code", "100"}], []}]}]},
ejabberd_router:route(
StateData#state.jid,
From, WPacket);
true ->
ok
end,
send_existing_presences(From, NewState),
send_new_presence(From, NewState),
Shift = count_stanza_shift(Nick, Els, NewState),
case send_history(From, Shift, NewState) of
true ->
@@ -1278,6 +1311,7 @@ change_nick(JID, Nick, StateData) ->
end, StateData#state.users),
NewStateData = StateData#state{users = Users},
send_nick_changing(JID, OldNick, NewStateData),
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
send_nick_changing(JID, OldNick, StateData) ->
@@ -1340,6 +1374,10 @@ lqueue_new(Max) ->
len = 0,
max = Max}.
%% If the message queue limit is set to 0, do not store messages.
lqueue_in(_Item, LQ = #lqueue{max = 0}) ->
LQ;
%% Otherwise, rotate messages in the queue store.
lqueue_in(Item, #lqueue{queue = Q1, len = Len, max = Max}) ->
Q2 = queue:in(Item, Q1),
if
@@ -1377,6 +1415,7 @@ add_message_to_history(FromNick, Packet, StateData) ->
Size = lists:flatlength(xml:element_to_string(SPacket)),
Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
StateData#state.history),
add_to_log(text, {FromNick, Packet}, StateData),
StateData#state{history = Q1}.
send_history(JID, Shift, StateData) ->
@@ -1544,29 +1583,41 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
catch send_kickban_presence(
JID, Reason, "307", SD),
set_role(JID, none, SD);
{JID, affiliation, none, Reason} ->
case (SD#state.config)#config.members_only of
true ->
catch send_kickban_presence(
JID, Reason, "321", SD),
SD1 = set_affiliation(JID, none, SD),
set_role(JID, none, SD1);
_ ->
SD1 = set_affiliation(JID, none, SD),
send_update_presence(JID, SD1),
SD1
end;
{JID, affiliation, outcast, Reason} ->
catch send_kickban_presence(
JID, Reason, "301", SD),
set_affiliation_and_reason(
JID, outcast, Reason,
set_role(JID, none, SD));
{JID, affiliation, A, Reason} when
{JID, affiliation, A, _Reason} when
(A == admin) or (A == owner) ->
SD1 = set_affiliation(JID, A, SD),
SD2 = set_role(JID, moderator, SD1),
send_update_presence(JID, SD2),
SD2;
{JID, affiliation, member, Reason} ->
{JID, affiliation, member, _Reason} ->
SD1 = set_affiliation(
JID, member, SD),
SD2 = set_role(JID, participant, SD1),
send_update_presence(JID, SD2),
SD2;
{JID, role, R, Reason} ->
{JID, role, R, _Reason} ->
SD1 = set_role(JID, R, SD),
catch send_new_presence(JID, SD1),
SD1;
{JID, affiliation, A, Reason} ->
{JID, affiliation, A, _Reason} ->
SD1 = set_affiliation(JID, A, SD),
send_update_presence(JID, SD1),
SD1
@@ -1593,7 +1644,7 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
end.
find_changed_items(UJID, UAffiliation, URole, [], _Lang, StateData, Res) ->
find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) ->
{result, Res};
find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items],
Lang, StateData, Res) ->
@@ -1652,10 +1703,28 @@ find_changed_items(UJID, UAffiliation, URole,
[StrAffiliation]),
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
SAffiliation ->
case can_change_ra(
UAffiliation, URole,
TAffiliation, TRole,
affiliation, SAffiliation) of
CanChangeRA =
case can_change_ra(
UAffiliation, URole,
TAffiliation, TRole,
affiliation, SAffiliation) of
nothing ->
nothing;
true ->
true;
check_owner ->
case search_affiliation(
owner, StateData) of
[{OJID, _}] ->
jlib:jid_remove_resource(OJID) /=
jlib:jid_tolower(jlib:jid_remove_resource(UJID));
_ ->
true
end;
_ ->
false
end,
case CanChangeRA of
nothing ->
find_changed_items(
UJID,
@@ -1673,7 +1742,7 @@ find_changed_items(UJID, UAffiliation, URole,
xml:get_path_s(
Item, [{elem, "reason"},
cdata])} | Res]);
_ ->
false ->
{error, ?ERR_NOT_ALLOWED}
end
end
@@ -1689,10 +1758,28 @@ find_changed_items(UJID, UAffiliation, URole,
[StrRole]),
{error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
SRole ->
case can_change_ra(
UAffiliation, URole,
TAffiliation, TRole,
role, SRole) of
CanChangeRA =
case can_change_ra(
UAffiliation, URole,
TAffiliation, TRole,
role, SRole) of
nothing ->
nothing;
true ->
true;
check_owner ->
case search_affiliation(
owner, StateData) of
[{OJID, _}] ->
jlib:jid_remove_resource(OJID) /=
jlib:jid_tolower(jlib:jid_remove_resource(UJID));
_ ->
true
end;
_ ->
false
end,
case CanChangeRA of
nothing ->
find_changed_items(
UJID,
@@ -1721,143 +1808,143 @@ find_changed_items(_UJID, _UAffiliation, _URole, _Items,
{error, ?ERR_BAD_REQUEST}.
can_change_ra(FAffiliation, FRole,
TAffiliation, TRole,
can_change_ra(_FAffiliation, _FRole,
TAffiliation, _TRole,
affiliation, Value)
when (TAffiliation == Value) ->
nothing;
can_change_ra(FAffiliation, FRole,
TAffiliation, TRole,
can_change_ra(_FAffiliation, _FRole,
_TAffiliation, TRole,
role, Value)
when (TRole == Value) ->
nothing;
can_change_ra(FAffiliation, FRole,
outcast, TRole,
can_change_ra(FAffiliation, _FRole,
outcast, _TRole,
affiliation, none)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(FAffiliation, FRole,
outcast, TRole,
can_change_ra(FAffiliation, _FRole,
outcast, _TRole,
affiliation, member)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(owner, FRole,
outcast, TRole,
can_change_ra(owner, _FRole,
outcast, _TRole,
affiliation, admin) ->
true;
can_change_ra(owner, FRole,
outcast, TRole,
can_change_ra(owner, _FRole,
outcast, _TRole,
affiliation, owner) ->
true;
can_change_ra(FAffiliation, FRole,
none, TRole,
can_change_ra(FAffiliation, _FRole,
none, _TRole,
affiliation, outcast)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(FAffiliation, FRole,
none, TRole,
can_change_ra(FAffiliation, _FRole,
none, _TRole,
affiliation, member)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(owner, FRole,
none, TRole,
can_change_ra(owner, _FRole,
none, _TRole,
affiliation, admin) ->
true;
can_change_ra(owner, FRole,
none, TRole,
can_change_ra(owner, _FRole,
none, _TRole,
affiliation, owner) ->
true;
can_change_ra(FAffiliation, FRole,
member, TRole,
can_change_ra(FAffiliation, _FRole,
member, _TRole,
affiliation, outcast)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(FAffiliation, FRole,
member, TRole,
can_change_ra(FAffiliation, _FRole,
member, _TRole,
affiliation, none)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(owner, FRole,
member, TRole,
can_change_ra(owner, _FRole,
member, _TRole,
affiliation, admin) ->
true;
can_change_ra(owner, FRole,
member, TRole,
can_change_ra(owner, _FRole,
member, _TRole,
affiliation, owner) ->
true;
can_change_ra(owner, FRole,
admin, TRole,
can_change_ra(owner, _FRole,
admin, _TRole,
affiliation, _Affiliation) ->
true;
can_change_ra(owner, FRole,
owner, TRole,
can_change_ra(owner, _FRole,
owner, _TRole,
affiliation, _Affiliation) ->
true;
can_change_ra(FAffiliation, FRole,
TAffiliation, TRole,
affiliation, Value) ->
check_owner;
can_change_ra(_FAffiliation, _FRole,
_TAffiliation, _TRole,
affiliation, _Value) ->
false;
can_change_ra(FAffiliation, moderator,
TAffiliation, visitor,
can_change_ra(_FAffiliation, moderator,
_TAffiliation, visitor,
role, none) ->
true;
can_change_ra(FAffiliation, moderator,
TAffiliation, visitor,
can_change_ra(_FAffiliation, moderator,
_TAffiliation, visitor,
role, participant) ->
true;
can_change_ra(FAffiliation, FRole,
TAffiliation, visitor,
can_change_ra(FAffiliation, _FRole,
_TAffiliation, visitor,
role, moderator)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(FAffiliation, moderator,
TAffiliation, participant,
can_change_ra(_FAffiliation, moderator,
_TAffiliation, participant,
role, none) ->
true;
can_change_ra(FAffiliation, moderator,
TAffiliation, participant,
can_change_ra(_FAffiliation, moderator,
_TAffiliation, participant,
role, visitor) ->
true;
can_change_ra(FAffiliation, FRole,
TAffiliation, participant,
can_change_ra(FAffiliation, _FRole,
_TAffiliation, participant,
role, moderator)
when (FAffiliation == owner) or (FAffiliation == admin) ->
true;
can_change_ra(FAffiliation, FRole,
can_change_ra(_FAffiliation, _FRole,
owner, moderator,
role, visitor) ->
false;
can_change_ra(owner, FRole,
TAffiliation, moderator,
can_change_ra(owner, _FRole,
_TAffiliation, moderator,
role, visitor) ->
true;
can_change_ra(FAffiliation, FRole,
can_change_ra(_FAffiliation, _FRole,
admin, moderator,
role, visitor) ->
false;
can_change_ra(admin, FRole,
TAffiliation, moderator,
can_change_ra(admin, _FRole,
_TAffiliation, moderator,
role, visitor) ->
true;
can_change_ra(FAffiliation, FRole,
can_change_ra(_FAffiliation, _FRole,
owner, moderator,
role, participant) ->
false;
can_change_ra(owner, FRole,
TAffiliation, moderator,
can_change_ra(owner, _FRole,
_TAffiliation, moderator,
role, participant) ->
true;
can_change_ra(FAffiliation, FRole,
can_change_ra(_FAffiliation, _FRole,
admin, moderator,
role, participant) ->
false;
can_change_ra(admin, FRole,
TAffiliation, moderator,
can_change_ra(admin, _FRole,
_TAffiliation, moderator,
role, participant) ->
true;
can_change_ra(FAffiliation, FRole,
TAffiliation, TRole,
role, Value) ->
can_change_ra(_FAffiliation, _FRole,
_TAffiliation, _TRole,
role, _Value) ->
false.
@@ -1883,17 +1970,20 @@ send_kickban_presence(JID, Reason, Code, StateData) ->
end
end,
lists:foreach(fun(J) ->
{ok, #user{nick = Nick}} =
?DICT:find(J, StateData#state.users),
add_to_log(kickban, {Nick, Reason, Code}, StateData),
send_kickban_presence1(J, Reason, Code, StateData)
end, LJIDs).
send_kickban_presence1(UJID, Reason, Code, StateData) ->
{ok, #user{jid = RealJID,
{ok, #user{jid = _RealJID,
nick = Nick}} =
?DICT:find(jlib:jid_tolower(UJID), StateData#state.users),
Affiliation = get_affiliation(UJID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
lists:foreach(
fun({LJID, Info}) ->
fun({_LJID, Info}) ->
ItemAttrs = [{"affiliation", SAffiliation},
{"role", "none"}],
ItemEls = case Reason of
@@ -1922,20 +2012,23 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
owner ->
{xmlelement, Name, Attrs, Els} = SubEl,
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, "x", Attrs1, Els1} = XEl] ->
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
case {xml:get_tag_attr_s("xmlns", XEl),
xml:get_tag_attr_s("type", XEl)} of
{?NS_XDATA, "cancel"} ->
{result, [], StateData};
{?NS_XDATA, "submit"} ->
set_config(XEl, StateData);
case check_allowed_log_change(XEl, StateData, From) of
allow -> set_config(XEl, StateData);
deny -> {error, ?ERR_BAD_REQUEST}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
end;
[{xmlelement, "destroy", Attrs1, Els1}] ->
destroy_room(Els1, StateData);
[{xmlelement, "destroy", _Attrs1, _Els1} = SubEl1] ->
destroy_room(SubEl1, StateData);
Items ->
process_admin_items_set(From, Items, Lang, StateData)
end;
@@ -1948,10 +2041,10 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
owner ->
{xmlelement, Name, Attrs, Els} = SubEl,
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:remove_cdata(Els) of
[] ->
get_config(Lang, StateData);
get_config(Lang, StateData, From);
[Item] ->
case xml:get_tag_attr("affiliation", Item) of
false ->
@@ -1980,6 +2073,15 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
{error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end.
check_allowed_log_change(XEl, StateData, From) ->
case lists:keymember("muc#roomconfig_enablelogging", 1,
jlib:parse_xdata_submit(XEl)) of
false ->
allow;
true ->
mod_muc_log:check_access_log(
StateData#state.server_host, From)
end.
-define(XFIELD(Type, Label, Var, Val),
@@ -2002,61 +2104,82 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
?XFIELD("text-private", Label, Var, Val)).
get_config(Lang, StateData) ->
get_config(Lang, StateData, From) ->
Config = StateData#state.config,
Res =
[{xmlelement, "title", [],
[{xmlcdata, translate:translate(Lang, "Configuration for ") ++
jlib:jid_to_string(StateData#state.jid)}]},
{xmlelement, "field", [{"type", "hidden"},
{"var", "FORM_TYPE"}],
[{xmlelement, "value", [],
[{xmlcdata, "http://jabber.org/protocol/muc#roomconfig"}]}]},
?STRINGXFIELD("Room title",
"title",
Config#config.title),
?BOOLXFIELD("Allow users to change subject?",
"allow_change_subj",
Config#config.allow_change_subj),
?BOOLXFIELD("Allow users to query other users?",
"allow_query_users",
Config#config.allow_query_users),
?BOOLXFIELD("Allow users to send private messages?",
"allow_private_messages",
Config#config.allow_private_messages),
?BOOLXFIELD("Make room public searchable?",
"public",
"muc#roomconfig_roomname",
Config#config.title),
?BOOLXFIELD("Make room persistent",
"muc#roomconfig_persistentroom",
Config#config.persistent),
?BOOLXFIELD("Make room public searchable",
"muc#roomconfig_publicroom",
Config#config.public),
?BOOLXFIELD("Make participants list public?",
?BOOLXFIELD("Make participants list public",
"public_list",
Config#config.public_list),
?BOOLXFIELD("Make room persistent?",
"persistent",
Config#config.persistent),
?BOOLXFIELD("Make room moderated?",
"moderated",
Config#config.moderated),
?BOOLXFIELD("Default users as members?",
"members_by_default",
Config#config.members_by_default),
?BOOLXFIELD("Make room members only?",
"members_only",
Config#config.members_only),
?BOOLXFIELD("Allow users to send invites?",
"allow_user_invites",
Config#config.allow_user_invites),
?BOOLXFIELD("Make room password protected?",
"password_protected",
?BOOLXFIELD("Make room password protected",
"muc#roomconfig_passwordprotectedroom",
Config#config.password_protected),
?PRIVATEXFIELD("Password",
"password",
"muc#roomconfig_roomsecret",
case Config#config.password_protected of
true -> Config#config.password;
false -> ""
end),
?BOOLXFIELD("Make room anonymous?",
"anonymous",
Config#config.anonymous),
?BOOLXFIELD("Enable logging?",
"logging",
Config#config.logging)
],
{xmlelement, "field",
[{"type", "list-single"},
{"label", translate:translate(Lang, "Present real JIDs to")},
{"var", "muc#roomconfig_whois"}],
[{xmlelement, "value", [], [{xmlcdata,
if Config#config.anonymous ->
"moderators";
true ->
"anyone"
end}]},
{xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}],
[{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]},
{xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}],
[{xmlelement, "value", [], [{xmlcdata, "anyone"}]}]}]},
?BOOLXFIELD("Make room members-only",
"muc#roomconfig_membersonly",
Config#config.members_only),
?BOOLXFIELD("Make room moderated",
"muc#roomconfig_moderatedroom",
Config#config.moderated),
?BOOLXFIELD("Default users as participants",
"members_by_default",
Config#config.members_by_default),
?BOOLXFIELD("Allow users to change subject",
"muc#roomconfig_changesubject",
Config#config.allow_change_subj),
?BOOLXFIELD("Allow users to send private messages",
"allow_private_messages",
Config#config.allow_private_messages),
?BOOLXFIELD("Allow users to query other users",
"allow_query_users",
Config#config.allow_query_users),
?BOOLXFIELD("Allow users to send invites",
"muc#roomconfig_allowinvites",
Config#config.allow_user_invites)
] ++
case mod_muc_log:check_access_log(
StateData#state.server_host, From) of
allow ->
[?BOOLXFIELD(
"Enable logging",
"muc#roomconfig_enablelogging",
Config#config.logging)];
_ -> []
end,
{result, [{xmlelement, "instructions", [],
[{xmlcdata,
translate:translate(
@@ -2076,7 +2199,10 @@ set_config(XEl, StateData) ->
_ ->
case set_xoption(XData, StateData#state.config) of
#config{} = Config ->
change_config(Config, StateData);
Res = change_config(Config, StateData),
{result, _, NSD} = Res,
add_to_log(roomconfig_change, [], NSD),
Res;
Err ->
Err
end
@@ -2085,7 +2211,9 @@ set_config(XEl, StateData) ->
-define(SET_BOOL_XOPT(Opt, Val),
case Val of
"0" -> set_xoption(Opts, Config#config{Opt = false});
"false" -> set_xoption(Opts, Config#config{Opt = false});
"1" -> set_xoption(Opts, Config#config{Opt = true});
"true" -> set_xoption(Opts, Config#config{Opt = true});
_ -> {error, ?ERR_BAD_REQUEST}
end).
@@ -2095,37 +2223,49 @@ set_config(XEl, StateData) ->
set_xoption([], Config) ->
Config;
set_xoption([{"title", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_roomname", [Val]} | Opts], Config) ->
?SET_STRING_XOPT(title, Val);
set_xoption([{"allow_change_subj", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_changesubject", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(allow_change_subj, Val);
set_xoption([{"allow_query_users", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(allow_query_users, Val);
set_xoption([{"allow_private_messages", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(allow_private_messages, Val);
set_xoption([{"public", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_publicroom", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(public, Val);
set_xoption([{"public_list", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(public_list, Val);
set_xoption([{"persistent", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_persistentroom", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(persistent, Val);
set_xoption([{"moderated", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_moderatedroom", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(moderated, Val);
set_xoption([{"members_by_default", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(members_by_default, Val);
set_xoption([{"members_only", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_membersonly", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(members_only, Val);
set_xoption([{"allow_user_invites", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_allowinvites", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(allow_user_invites, Val);
set_xoption([{"password_protected", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_passwordprotectedroom", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(password_protected, Val);
set_xoption([{"password", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_roomsecret", [Val]} | Opts], Config) ->
?SET_STRING_XOPT(password, Val);
set_xoption([{"anonymous", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(anonymous, Val);
set_xoption([{"logging", [Val]} | Opts], Config) ->
set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) ->
case Val of
"moderators" ->
?SET_BOOL_XOPT(anonymous, "1");
"anyone" ->
?SET_BOOL_XOPT(anonymous, "0");
_ ->
{error, ?ERR_BAD_REQUEST}
end;
set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) ->
?SET_BOOL_XOPT(logging, Val);
set_xoption([_ | Opts], Config) ->
set_xoption([{"FORM_TYPE", _} | Opts], Config) ->
%% Ignore our FORM_TYPE
set_xoption(Opts, Config);
set_xoption([_ | _Opts], _Config) ->
{error, ?ERR_BAD_REQUEST}.
@@ -2139,8 +2279,29 @@ change_config(Config, StateData) ->
mod_muc:forget_room(NSD#state.host, NSD#state.room);
{false, false} ->
ok
end,
{result, [], NSD}.
end,
case {(StateData#state.config)#config.members_only,
Config#config.members_only} of
{false, true} ->
NSD1 = remove_nonmembers(NSD),
{result, [], NSD1};
_ ->
{result, [], NSD}
end.
remove_nonmembers(StateData) ->
lists:foldl(
fun({_LJID, #user{jid = JID}}, SD) ->
Affiliation = get_affiliation(JID, SD),
case Affiliation of
none ->
catch send_kickban_presence(
JID, "", "322", SD),
set_role(JID, none, SD);
_ ->
SD
end
end, StateData, ?DICT:to_list(StateData#state.users)).
-define(CASE_CONFIG_OPT(Opt),
@@ -2203,17 +2364,15 @@ make_opts(StateData) ->
destroy_room(DEls, StateData) ->
destroy_room(DEl, StateData) ->
lists:foreach(
fun({LJID, Info}) ->
fun({_LJID, Info}) ->
Nick = Info#user.nick,
ItemAttrs = [{"affiliation", "none"},
{"role", "none"}],
Packet = {xmlelement, "presence", [{"type", "unavailable"}],
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
[{xmlelement, "item", ItemAttrs, []},
{xmlelement, "destroy", [],
DEls}]}]},
[{xmlelement, "item", ItemAttrs, []}, DEl]}]},
ejabberd_router:route(
jlib:jid_replace_resource(StateData#state.jid, Nick),
Info#user.jid,
@@ -2243,10 +2402,10 @@ destroy_room(DEls, StateData) ->
?FEATURE(Fiffalse)
end).
process_iq_disco_info(From, set, Lang, StateData) ->
process_iq_disco_info(_From, set, _Lang, _StateData) ->
{error, ?ERR_NOT_ALLOWED};
process_iq_disco_info(From, get, Lang, StateData) ->
process_iq_disco_info(_From, get, Lang, StateData) ->
Config = StateData#state.config,
{result, [{xmlelement, "identity",
[{"category", "conference"},
@@ -2266,13 +2425,33 @@ process_iq_disco_info(From, get, Lang, StateData) ->
"muc_moderated", "muc_unmoderated"),
?CONFIG_OPT_TO_FEATURE(Config#config.password_protected,
"muc_passwordprotected", "muc_unsecured")
], StateData}.
] ++ iq_disco_info_extras(Lang, StateData), StateData}.
-define(RFIELDT(Type, Var, Val),
{xmlelement, "field", [{"type", Type}, {"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
process_iq_disco_items(From, set, Lang, StateData) ->
-define(RFIELD(Label, Var, Val),
{xmlelement, "field", [{"label", translate:translate(Lang, Label)},
{"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
iq_disco_info_extras(Lang, StateData) ->
Len = length(?DICT:to_list(StateData#state.users)),
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
[?RFIELDT("hidden", "FORM_TYPE",
"http://jabber.org/protocol/muc#roominfo"),
?RFIELD("Description", "muc#roominfo_description",
(StateData#state.config)#config.title),
%?RFIELD("Subject", "muc#roominfo_subject", StateData#state.subject),
?RFIELD("Number of occupants", "muc#roominfo_occupants",
integer_to_list(Len))
]}].
process_iq_disco_items(_From, set, _Lang, _StateData) ->
{error, ?ERR_NOT_ALLOWED};
process_iq_disco_items(From, get, Lang, StateData) ->
process_iq_disco_items(From, get, _Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
case ((StateData#state.config)#config.public_list == true) orelse
@@ -2282,7 +2461,7 @@ process_iq_disco_items(From, get, Lang, StateData) ->
true ->
UList =
lists:map(
fun({LJID, Info}) ->
fun({_LJID, Info}) ->
Nick = Info#user.nick,
{xmlelement, "item",
[{"jid", jlib:jid_to_string(
@@ -2314,11 +2493,11 @@ check_invitation(From, Els, StateData) ->
CanInvite = (StateData#state.config)#config.allow_user_invites
orelse (FAffiliation == admin) orelse (FAffiliation == owner),
case xml:remove_cdata(Els) of
[{xmlelement, "x", Attrs1, Els1} = XEl] ->
[{xmlelement, "x", _Attrs1, Els1} = XEl] ->
case xml:get_tag_attr_s("xmlns", XEl) of
?NS_MUC_USER ->
case xml:remove_cdata(Els1) of
[{xmlelement, "invite", Attrs2, Els2} = InviteEl] ->
[{xmlelement, "invite", Attrs2, _Els2} = InviteEl] ->
case jlib:string_to_jid(
xml:get_attr_s("to", Attrs2)) of
error ->
@@ -2378,3 +2557,16 @@ check_invitation(From, Els, StateData) ->
error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
add_to_log(Type, Data, StateData) ->
case (StateData#state.config)#config.logging of
true ->
mod_muc_log:add_to_log(
StateData#state.server_host, Type, Data,
StateData#state.jid, make_opts(StateData));
false ->
ok
end.
-4
View File
@@ -37,8 +37,6 @@ start(Host, _Opts) ->
update_table(),
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:add(offline_subscription_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:add(remove_user, Host,
@@ -83,8 +81,6 @@ receive_all(Msgs) ->
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(offline_subscription_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(resend_offline_messages_hook, Host,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:delete(remove_user, Host,
+5 -26
View File
@@ -5,7 +5,6 @@
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_offline_odbc).
-author('alexey@sevcom.net').
@@ -29,8 +28,6 @@
start(Host, _Opts) ->
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:add(offline_subscription_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:add(remove_user, Host,
@@ -68,14 +65,9 @@ loop(Host) ->
ejabberd_odbc:escape(
lists:flatten(
xml:element_to_string(Packet))),
["insert into spool(username, xml) "
"values ('", Username, "', '",
XML,
"');"]
odbc_queries:add_spool_sql(Username, XML)
end, Msgs),
case catch ejabberd_odbc:sql_query(
Host,
["begin; ", Query, " commit"]) of
case catch odbc_queries:add_spool(Host, Query) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~n", [Reason]);
_ ->
@@ -98,8 +90,6 @@ receive_all(Msgs) ->
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(offline_subscription_hook, Host,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(resend_offline_messages_hook, Host,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:delete(remove_user, Host,
@@ -212,17 +202,8 @@ pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
EUser = ejabberd_odbc:escape(LUser),
case ejabberd_odbc:sql_query(
LServer,
["begin;"
"select username, xml from spool where username='", EUser, "'"
" order by seq;"
"delete from spool where username='", EUser, "';"
"commit"]) of
[{updated, undefined},
{selected, ["username","xml"], Rs},
{updated, _},
{updated, undefined}] ->
case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
{atomic, {selected, ["username","xml"], Rs}} ->
Ls ++ lists:flatmap(
fun({_, XML}) ->
case xml_stream:parse_element(XML) of
@@ -251,7 +232,5 @@ remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_query(
LServer,
["delete from spool where username='", Username, "'"]).
odbc_queries:del_spool_msg(LServer, Username).
+6 -2
View File
@@ -362,7 +362,9 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
jlib:make_jid(LUser, ?MYNAME, ""),
jlib:make_jid(LUser, ?MYNAME, ""),
{xmlelement, "broadcast", [],
[{privacy_list, #userlist{name = Name, list = []}}]}),
[{privacy_list,
#userlist{name = Name, list = []},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
@@ -391,7 +393,9 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
jlib:make_jid(LUser, ?MYNAME, ""),
jlib:make_jid(LUser, ?MYNAME, ""),
{xmlelement, "broadcast", [],
[{privacy_list, #userlist{name = Name, list = List}}]}),
[{privacy_list,
#userlist{name = Name, list = List},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
+185 -106
View File
@@ -10,15 +10,13 @@
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
-behaviour(gen_mod).
-export([start/2,
init/4,
loop/2,
stop/1,
system_continue/3,
system_terminate/4,
system_code_change/4]).
%% API
-export([start_link/2,
start/2,
stop/1]).
-export([delete_item/3,
set_entities/4,
@@ -28,9 +26,15 @@
get_node_config/4,
set_node_config/4]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {host, server_host, access}).
-define(DICT, dict).
-define(MAXITEMS, 20).
-define(MAX_PAYLOAD_SIZE, 100000).
@@ -45,60 +49,35 @@
-record(item, {id, publisher, payload}).
-define(PROCNAME, ejabberd_mod_pubsub).
start(Host, Opts) ->
mnesia:create_table(pubsub_node,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
MyHost = gen_mod:get_opt(host, Opts, "pubsub." ++ Host),
update_table(MyHost),
mnesia:add_table_index(pubsub_node, host_parent),
ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
proc_lib:spawn_link(?MODULE, init,
[MyHost, Host, ServedHosts, self()])).
-define(MYJID, #jid{user = "", server = Host, resource = "",
luser = "", lserver = Host, lresource = ""}).
init(Host, ServerHost, ServedHosts, Parent) ->
ejabberd_router:register_route(Host),
create_new_node(Host, ["pubsub"], ?MYJID),
create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
create_new_node(Host, ["home"], ?MYJID),
create_new_node(Host, ["home", ServerHost], ?MYJID),
lists:foreach(fun(H) ->
create_new_node(Host, ["home", H], ?MYJID)
end, ServedHosts),
ets:new(gen_mod:get_module_proc(Host, pubsub_presence), [set, named_table]),
loop(Host, Parent).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
loop(Host, Parent) ->
receive
{route, From, To, Packet} ->
case catch do_route(To#jid.lserver, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Host, Parent);
{room_destroyed, Room} ->
ets:delete(muc_online_room, Room),
loop(Host, Parent);
stop ->
ejabberd_router:unregister_route(Host),
ok;
reload ->
?MODULE:loop(Host, Parent);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], Host);
_ ->
loop(Host, Parent)
end.
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
%%% API functions
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:stop_child(ejabberd_sup, Proc).
delete_item(From, Node, ItemID) ->
delete_item(get_host(), From, Node, ItemID).
@@ -124,9 +103,100 @@ get_host() ->
timeout
end.
%%% Internal functions
%%====================================================================
%% gen_server callbacks
%%====================================================================
do_route(Host, From, To, Packet) ->
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([ServerHost, Opts]) ->
mnesia:create_table(pubsub_node,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
Host = gen_mod:get_opt(host, Opts, "pubsub." ++ ServerHost),
update_table(Host),
mnesia:add_table_index(pubsub_node, host_parent),
ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
Access = gen_mod:get_opt(access_createnode, Opts, all),
ejabberd_router:register_route(Host),
create_new_node(Host, ["pubsub"], ?MYJID),
create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
create_new_node(Host, ["home"], ?MYJID),
create_new_node(Host, ["home", ServerHost], ?MYJID),
lists:foreach(fun(H) ->
create_new_node(Host, ["home", H], ?MYJID)
end, ServedHosts),
ets:new(gen_mod:get_module_proc(Host, pubsub_presence),
[set, named_table]),
{ok, #state{host = Host, server_host = ServerHost, access = Access}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet},
#state{server_host = ServerHost, access = Access} = State) ->
case catch do_route(To#jid.lserver, ServerHost, Access, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(Host, ServerHost, Access, From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case To of
#jid{luser = "", lresource = ""} ->
@@ -164,7 +234,7 @@ do_route(Host, From, To, Packet) ->
#iq{type = Type, xmlns = ?NS_PUBSUB = XMLNS,
sub_el = SubEl} = IQ ->
Res =
case iq_pubsub(Host, From, Type, SubEl) of
case iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) of
{result, IQRes} ->
jlib:iq_to_xml(
IQ#iq{type = result,
@@ -236,14 +306,6 @@ do_route(Host, From, To, Packet) ->
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
node_to_string(Node) ->
string:strip(lists:flatten(lists:map(fun(S) -> [S, "/"] end, Node)),
right, $/).
@@ -256,7 +318,7 @@ iq_disco_info(SNode) ->
[{xmlelement, "identity",
[{"category", "pubsub"},
{"type", "generic"},
{"name", "ejabberd/mod_pubsub"}], []},
{"name", "Publish-Subscribe"}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB_EVENT}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB_OWNER}], []},
@@ -266,32 +328,50 @@ iq_disco_info(SNode) ->
[]
end.
iq_disco_items(Host, From, SNode) ->
Node = string:tokens(SNode, "/"),
{Node,ItemID} = case SNode of
[] ->
{[],none};
_ ->
Tokens = string:tokens(SNode, "!"),
NodeList = string:tokens(lists:nth(1, Tokens), "/"),
ItemName = case length(Tokens) of
2 -> lists:nth(2, Tokens);
_ -> none
end,
{NodeList, ItemName}
end,
NodeFull = string:tokens(SNode,"/"),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info}] ->
SubNodes = mnesia:index_read(pubsub_node,
case ItemID of
none ->
SubNodes = mnesia:index_read(pubsub_node,
{Host, Node},
#pubsub_node.host_parent),
SubItems =
lists:map(fun(#pubsub_node{host_node = {_, N}}) ->
SubItems = lists:map(fun(#pubsub_node{host_node = {_, N}}) ->
SN = node_to_string(N),
{xmlelement, "item",
[{"jid", Host},
{"node", SN},
{"name", lists:last(N)}], []}
end, SubNodes),
SN = node_to_string(Node),
Items =
lists:map(fun(#item{id = Name}) ->
SN = node_to_string(Node),
Items = lists:map(fun(#item{id = Name}) ->
RealName = case Name of
[] -> "item";
_ -> Name
end,
{xmlelement, "item",
[{"jid", Host},
{"node", SN ++ "!" ++ Name},
{"name", Name}], []}
{"name", RealName}], []}
end, Info#nodeinfo.items),
SubItems ++ Items;
SubItems ++ Items;
_ ->
[]
end;
[] ->
case Node of
[] ->
@@ -322,8 +402,6 @@ iq_disco_items(Host, From, SNode) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
iq_get_vcard(Lang) ->
[{xmlelement, "FN", [],
[{xmlcdata, "ejabberd/mod_pubsub"}]},
@@ -334,10 +412,10 @@ iq_get_vcard(Lang) ->
[{xmlcdata, translate:translate(
Lang,
"ejabberd pub/sub module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
iq_pubsub(Host, From, Type, SubEl) ->
iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) ->
{xmlelement, _, _, SubEls} = SubEl,
case xml:remove_cdata(SubEls) of
[{xmlelement, Name, Attrs, Els}] ->
@@ -345,7 +423,7 @@ iq_pubsub(Host, From, Type, SubEl) ->
Node = string:tokens(SNode, "/"),
case {Type, Name} of
{set, "create"} ->
create_new_node(Host, Node, From);
create_new_node(Host, Node, From, ServerHost, Access);
{set, "publish"} ->
case xml:remove_cdata(Els) of
[{xmlelement, "item", ItemAttrs, Payload}] ->
@@ -424,13 +502,17 @@ iq_pubsub(Host, From, Type, SubEl) ->
%% Create new pubsub nodes
%% This function is used during init to create the first bootstrap nodes
create_new_node(Host, Node, Owner) ->
%% This is the case use during "bootstrapping to create the initial
%% hierarchy. Should always be ... undefined,all
create_new_node(Host, Node, Owner, undefined, all).
create_new_node(Host, Node, Owner, ServerHost, Access) ->
case Node of
[] ->
{LOU, LOS, _} = jlib:jid_tolower(Owner),
HomeNode = ["home", LOS, LOU],
create_new_node(Host, HomeNode, Owner),
create_new_node(Host, HomeNode, Owner, ServerHost, Access),
NewNode = ["home", LOS, LOU, randoms:get_string()],
create_new_node(Host, NewNode, Owner);
create_new_node(Host, NewNode, Owner, ServerHost, Access);
_ ->
LOwner = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Parent = lists:sublist(Node, length(Node) - 1),
@@ -465,7 +547,7 @@ create_new_node(Host, Node, Owner) ->
end
end
end,
case check_create_permission(Host, Node, Owner) of
case check_create_permission(Host, Node, Owner, ServerHost, Access) of
true ->
case mnesia:transaction(F) of
{atomic, ok} ->
@@ -992,14 +1074,23 @@ subscription_to_string(Subscription) ->
end.
check_create_permission(Host, Node, Owner) ->
if
Owner#jid.lserver == Host ->
true;
true ->
#jid{luser = User, lserver = Server} = Owner,
case Node of
["home", Server, User | _] ->
check_create_permission(Host, Node, Owner, ServerHost, Access) ->
#jid{luser = User, lserver = Server, lresource = Resource} = Owner,
case acl:match_rule(ServerHost, Access, {User, Server, Resource}) of
allow ->
if Server == Host ->
true;
true ->
case Node of
["home", Server, User | _] ->
true;
_ ->
false
end
end;
_ ->
case Owner of
?MYJID ->
true;
_ ->
false
@@ -1261,18 +1352,6 @@ broadcast_config_notification(Host, Node, Lang) ->
system_continue(Parent, _, State) ->
loop(State, Parent).
system_terminate(Reason, Parent, _, State) ->
exit(Reason).
system_code_change(State, _Mod, Ver, _Extra) ->
{ok, State}.
iq_pubsub_owner(Host, From, Type, Lang, SubEl) ->
{xmlelement, _, _, SubEls} = SubEl,
case xml:remove_cdata(SubEls) of
+12 -4
View File
@@ -8,7 +8,6 @@
-module(mod_register).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_mod).
@@ -59,7 +58,7 @@ unauthenticated_iq_register(Acc, _Server, _IQ) ->
Acc.
process_iq(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
#iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ) ->
case Type of
set ->
UTag = xml:get_subtag(SubEl, "username"),
@@ -104,9 +103,18 @@ process_iq(From, To,
end;
(UTag == false) and (RTag /= false) ->
case From of
#jid{user = User, lserver = Server} ->
#jid{user = User,
lserver = Server,
resource = Resource} ->
ResIQ = #iq{type = result, xmlns = ?NS_REGISTER,
id = ID,
sub_el = [SubEl]},
ejabberd_router:route(
jlib:make_jid(User, Server, Resource),
jlib:make_jid(User, Server, Resource),
jlib:iq_to_xml(ResIQ)),
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = [SubEl]};
ignore;
_ ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+104 -52
View File
@@ -17,7 +17,8 @@
process_local_iq/3,
get_user_roster/2,
get_subscription_lists/3,
in_subscription/5,
get_in_pending_subscriptions/3,
in_subscription/6,
out_subscription/4,
set_items/3,
remove_user/2,
@@ -46,6 +47,8 @@ start(Host, Opts) ->
?MODULE, get_jid_info, 50),
ejabberd_hooks:add(remove_user, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(resend_subscription_requests_hook, Host,
?MODULE, get_in_pending_subscriptions, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER,
?MODULE, process_iq, IQDisc).
@@ -62,6 +65,8 @@ stop(Host) ->
?MODULE, get_jid_info, 50),
ejabberd_hooks:delete(remove_user, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(resend_subscription_requests_hook, Host,
?MODULE, get_in_pending_subscriptions, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER).
@@ -130,8 +135,6 @@ get_user_roster(Acc, US) ->
end.
item_to_xml(Item) ->
Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}],
Attrs2 = case Item#roster.name of
@@ -160,12 +163,11 @@ item_to_xml(Item) ->
_ ->
Attrs3
end,
Attrs = Attrs4 ++ Item#roster.xattrs,
SubEls1 = lists:map(fun(G) ->
{xmlelement, "group", [], [{xmlcdata, G}]}
end, Item#roster.groups),
SubEls = SubEls1 ++ Item#roster.xs,
{xmlelement, "item", Attrs, SubEls}.
{xmlelement, "item", Attrs4, SubEls}.
process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
@@ -193,7 +195,6 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
I#roster{jid = JID,
name = "",
groups = [],
xattrs = [],
xs = []}
end,
Item1 = process_item_attrs(Item, Attrs),
@@ -204,7 +205,11 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
_ ->
mnesia:write(Item2)
end,
{Item, Item2}
%% If the item exist in shared roster, take the
%% subscription information from there:
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2, [LServer]),
{Item, Item3}
end,
case mnesia:transaction(F) of
{atomic, {OldItem, Item}} ->
@@ -274,9 +279,7 @@ process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
"ask" ->
process_item_attrs(Item, Attrs);
_ ->
XAttrs = Item#roster.xattrs,
process_item_attrs(Item#roster{xattrs = [{Attr, Val} | XAttrs]},
Attrs)
process_item_attrs(Item, Attrs)
end;
process_item_attrs(Item, []) ->
Item.
@@ -374,13 +377,13 @@ ask_to_pending(Ask) -> Ask.
in_subscription(_, User, Server, JID, Type) ->
process_subscription(in, User, Server, JID, Type).
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type, Reason).
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type).
process_subscription(out, User, Server, JID, Type, []).
process_subscription(Direction, User, Server, JID1, Type) ->
process_subscription(Direction, User, Server, JID1, Type, Reason) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
@@ -415,12 +418,18 @@ process_subscription(Direction, User, Server, JID1, Type) ->
Item#roster.ask,
Type)
end,
AskMessage = case NewState of
{_, both} -> Reason;
{_, in} -> Reason;
_ -> ""
end,
case NewState of
none ->
{none, AutoReply};
{Subscription, Pending} ->
NewItem = Item#roster{subscription = Subscription,
ask = Pending},
ask = Pending,
askmessage = list_to_binary(AskMessage)},
mnesia:write(NewItem),
{{push, NewItem}, AutoReply}
end
@@ -502,7 +511,7 @@ out_state_change(none, none, subscribe) -> {none, out};
out_state_change(none, none, subscribed) -> none;
out_state_change(none, none, unsubscribe) -> none;
out_state_change(none, none, unsubscribed) -> none;
out_state_change(none, out, subscribe) -> none;
out_state_change(none, out, subscribe) -> {none, out}; %% We need to resend query (RFC3921, section 9.2)
out_state_change(none, out, subscribed) -> none;
out_state_change(none, out, unsubscribe) -> {none, none};
out_state_change(none, out, unsubscribed) -> none;
@@ -630,13 +639,44 @@ process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
"ask" ->
process_item_attrs_ws(Item, Attrs);
_ ->
XAttrs = Item#roster.xattrs,
process_item_attrs_ws(Item#roster{xattrs = [{Attr, Val} | XAttrs]},
Attrs)
process_item_attrs_ws(Item, Attrs)
end;
process_item_attrs_ws(Item, []) ->
Item.
get_in_pending_subscriptions(Ls, User, Server) ->
JID = jlib:make_jid(User, Server, ""),
US = {JID#jid.luser, JID#jid.lserver},
case mnesia:dirty_index_read(roster, US, #roster.us) of
Result when list(Result) ->
Ls ++ lists:map(
fun(R) ->
Message = R#roster.askmessage,
Status = if is_binary(Message) ->
binary_to_list(Message);
true ->
""
end,
{xmlelement, "presence",
[{"from", jlib:jid_to_string(R#roster.jid)},
{"to", jlib:jid_to_string(JID)},
{"type", "subscribe"}],
[{xmlelement, "status", [],
[{xmlcdata, Status}]}]}
end,
lists:filter(
fun(R) ->
case R#roster.ask of
in -> true;
both -> true;
_ -> false
end
end,
Result));
_ ->
Ls
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -673,42 +713,54 @@ update_table() ->
Fields ->
ok;
[uj, user, jid, name, subscription, ask, groups, xattrs, xs] ->
?INFO_MSG("Converting roster table from "
"{uj, user, jid, name, subscription, ask, groups, xattrs, xs} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_roster_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, roster},
{attributes, record_info(fields, roster)}]),
mnesia:del_table_index(roster, user),
mnesia:transform_table(roster, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_roster_tmp_table),
mnesia:foldl(
fun(#roster{usj = {U, JID}, us = U} = R, _) ->
mnesia:dirty_write(
mod_roster_tmp_table,
R#roster{usj = {U, Host, JID},
us = {U, Host}})
end, ok, roster)
end,
mnesia:transaction(F1),
mnesia:clear_table(roster),
F2 = fun() ->
mnesia:write_lock_table(roster),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_roster_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_roster_tmp_table);
convert_table1(Fields);
[usj, us, jid, name, subscription, ask, groups, xattrs, xs] ->
convert_table2(Fields);
_ ->
?INFO_MSG("Recreating roster table", []),
mnesia:transform_table(roster, ignore, Fields)
end.
%% Convert roster table to support virtual host
convert_table1(Fields) ->
?INFO_MSG("Virtual host support: converting roster table from "
"{uj, user, jid, name, subscription, ask, groups, xattrs, xs} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_roster_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, roster},
{attributes, record_info(fields, roster)}]),
mnesia:del_table_index(roster, user),
mnesia:transform_table(roster, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_roster_tmp_table),
mnesia:foldl(
fun(#roster{usj = {U, JID}, us = U} = R, _) ->
mnesia:dirty_write(
mod_roster_tmp_table,
R#roster{usj = {U, Host, JID},
us = {U, Host}})
end, ok, roster)
end,
mnesia:transaction(F1),
mnesia:clear_table(roster),
F2 = fun() ->
mnesia:write_lock_table(roster),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_roster_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_roster_tmp_table).
%% Convert roster table: xattrs fields become
convert_table2(Fields) ->
?INFO_MSG("Converting roster table from "
"{usj, us, jid, name, subscription, ask, groups, xattrs, xs} format", []),
mnesia:transform_table(roster, ignore, Fields).
+1 -1
View File
@@ -13,6 +13,6 @@
subscription = none,
ask = none,
groups = [],
xattrs = [],
askmessage = [],
xs = []}).
+150 -208
View File
@@ -8,7 +8,6 @@
-module(mod_roster_odbc).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_mod).
@@ -17,7 +16,8 @@
process_local_iq/3,
get_user_roster/2,
get_subscription_lists/3,
in_subscription/5,
get_in_pending_subscriptions/3,
in_subscription/6,
out_subscription/4,
set_items/3,
remove_user/2,
@@ -25,14 +25,7 @@
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(roster, {user,
jid,
name = "",
subscription = none,
ask = none,
groups = []
}).
-include("mod_roster.hrl").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
@@ -48,6 +41,8 @@ start(Host, Opts) ->
?MODULE, get_jid_info, 50),
ejabberd_hooks:add(remove_user, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(resend_subscription_requests_hook, Host,
?MODULE, get_in_pending_subscriptions, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER,
?MODULE, process_iq, IQDisc).
@@ -64,6 +59,8 @@ stop(Host) ->
?MODULE, get_jid_info, 50),
ejabberd_hooks:delete(remove_user, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(resend_subscription_requests_hook, Host,
?MODULE, get_in_pending_subscriptions, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER).
@@ -74,8 +71,8 @@ stop(Host) ->
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
case ?MYNAME of
LServer ->
case lists:member(LServer, ?MYHOSTS) of
true ->
ResIQ = process_local_iq(From, To, IQ),
ejabberd_router:route(From, From,
jlib:iq_to_xml(ResIQ)),
@@ -89,8 +86,8 @@ process_iq(From, To, IQ) ->
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
case ?MYNAME of
LServer ->
case lists:member(LServer, ?MYHOSTS) of
true ->
process_local_iq(From, To, IQ);
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
@@ -108,44 +105,13 @@ process_local_iq(From, To, #iq{type = Type} = IQ) ->
process_iq_get(From, _To, #iq{sub_el = SubEl} = IQ) ->
process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
LUser = From#jid.luser,
LServer = From#jid.lserver,
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
["select username, jid, nick, subscription, ask, "
"server, subscribe, type from rosterusers "
"where username='", Username, "'"]) of
{selected, ["username", "jid", "nick", "subscription", "ask",
"server", "subscribe", "type"],
Items} when is_list(Items) ->
JIDGroups = case catch ejabberd_odbc:sql_query(
LServer,
["select jid, grp from rostergroups "
"where username='", Username, "'"]) of
{selected, ["jid","grp"],
JGrps} when is_list(JGrps) ->
JGrps;
_ ->
[]
end,
XItems = lists:flatmap(
fun(I) ->
case raw_to_record(I) of
error ->
[];
R ->
SJID = jlib:jid_to_string(R#roster.jid),
Groups = lists:flatmap(
fun({S, G}) when S == SJID ->
[G];
(_) ->
[]
end, JIDGroups),
[item_to_xml(R#roster{groups = Groups})]
end
end, Items),
US = {LUser, LServer},
case catch ejabberd_hooks:run_fold(roster_get, To#jid.lserver, [], [US]) of
Items when is_list(Items) ->
XItems = lists:map(fun item_to_xml/1, Items),
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_ROSTER}],
@@ -156,21 +122,31 @@ process_iq_get(From, _To, #iq{sub_el = SubEl} = IQ) ->
get_user_roster(Acc, {LUser, LServer}) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
["select username, jid, nick, subscription, ask, "
"server, subscribe, type from rosterusers "
"where username='", Username, "'"]) of
case catch odbc_queries:get_roster(LServer, Username) of
{selected, ["username", "jid", "nick", "subscription", "ask",
"server", "subscribe", "type"],
"askmessage", "server", "subscribe", "type"],
Items} when is_list(Items) ->
JIDGroups = case catch odbc_queries:get_roster_jid_groups(LServer, Username) of
{selected, ["jid","grp"], JGrps}
when is_list(JGrps) ->
JGrps;
_ ->
[]
end,
RItems = lists:flatmap(
fun(I) ->
case raw_to_record(I) of
case raw_to_record(LServer, I) of
error ->
[];
R ->
[R]
SJID = jlib:jid_to_string(R#roster.jid),
Groups = lists:flatmap(
fun({S, G}) when S == SJID ->
[G];
(_) ->
[]
end, JIDGroups),
[R#roster{groups = Groups}]
end
end, Items),
RItems ++ Acc;
@@ -232,56 +208,37 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
F = fun() ->
{selected,
["username", "jid", "nick", "subscription",
"ask", "server", "subscribe", "type"],
Res} =
ejabberd_odbc:sql_query_t(
["select username, jid, nick, subscription, "
"ask, server, subscribe, type from rosterusers "
"where username='", Username, "' "
"and jid='", SJID, "'"]),
"ask", "askmessage", "server", "subscribe", "type"],
Res} = odbc_queries:get_roster_by_jid(LServer, Username, SJID),
Item = case Res of
[] ->
#roster{user = LUser,
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = LJID};
[I] ->
(raw_to_record(I))#roster{user = LUser,
jid = JID,
name = ""}
(raw_to_record(LServer, I))#roster{
usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = LJID,
name = ""}
end,
Item1 = process_item_attrs(Item, Attrs),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove ->
ejabberd_odbc:sql_query_t(
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"delete from rostergroups "
" where username='", Username, "' "
" and jid='", SJID, "'"]);
odbc_queries:del_roster(LServer, Username, SJID);
_ ->
ItemVals = record_to_string(Item2),
ItemGroups = groups_to_string(Item2),
ejabberd_odbc:sql_query_t(
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"insert into rosterusers("
" username, jid, nick, "
" subscription, ask, "
" server, subscribe, type) "
" values ", ItemVals, ";"
"delete from rostergroups "
" where username='", Username, "' "
" and jid='", SJID, "';",
[["insert into rostergroups("
" username, jid, grp) "
" values ", ItemGroup, ";"] ||
ItemGroup <- ItemGroups]])
odbc_queries:update_roster(LServer, Username, SJID, ItemVals, ItemGroups)
end,
{Item, Item2}
%% If the item exist in shared roster, take the
%% subscription information from there:
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2, [LServer]),
{Item, Item3}
end,
case ejabberd_odbc:sql_transaction(LServer, F) of
case odbc_queries:sql_transaction(LServer, F) of
{atomic, {OldItem, Item}} ->
push_item(User, LServer, To, Item),
case Item#roster.subscription of
@@ -333,7 +290,7 @@ process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
error ->
process_item_attrs(Item, Attrs);
JID1 ->
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
process_item_attrs(Item#roster{jid = JID}, Attrs)
end;
"name" ->
@@ -413,33 +370,29 @@ get_subscription_lists(_, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
["select username, jid, nick, subscription, ask, "
"server, subscribe, type from rosterusers "
"where username='", Username, "'"]) of
case catch odbc_queries:get_roster(LServer, Username) of
{selected, ["username", "jid", "nick", "subscription", "ask",
"server", "subscribe", "type"],
"askmessage", "server", "subscribe", "type"],
Items} when is_list(Items) ->
fill_subscription_lists(Items, [], []);
fill_subscription_lists(LServer, Items, [], []);
_ ->
{[], []}
end.
fill_subscription_lists([RawI | Is], F, T) ->
I = raw_to_record(RawI),
fill_subscription_lists(LServer, [RawI | Is], F, T) ->
I = raw_to_record(LServer, RawI),
J = I#roster.jid,
case I#roster.subscription of
both ->
fill_subscription_lists(Is, [J | F], [J | T]);
fill_subscription_lists(LServer, Is, [J | F], [J | T]);
from ->
fill_subscription_lists(Is, [J | F], T);
fill_subscription_lists(LServer, Is, [J | F], T);
to ->
fill_subscription_lists(Is, F, [J | T]);
fill_subscription_lists(LServer, Is, F, [J | T]);
_ ->
fill_subscription_lists(Is, F, T)
fill_subscription_lists(LServer, Is, F, T)
end;
fill_subscription_lists([], F, T) ->
fill_subscription_lists(_LServer, [], F, T) ->
{F, T}.
ask_to_pending(subscribe) -> out;
@@ -448,13 +401,13 @@ ask_to_pending(Ask) -> Ask.
in_subscription(_, User, Server, JID, Type) ->
process_subscription(in, User, Server, JID, Type).
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type, Reason).
out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type).
process_subscription(out, User, Server, JID, Type, []).
process_subscription(Direction, User, Server, JID1, Type) ->
process_subscription(Direction, User, Server, JID1, Type, Reason) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LJID = jlib:jid_tolower(JID1),
@@ -462,21 +415,14 @@ process_subscription(Direction, User, Server, JID1, Type) ->
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
F = fun() ->
Item =
case ejabberd_odbc:sql_query_t(
["select username, jid, nick, subscription, ask, "
"server, subscribe, type from rosterusers "
"where username='", Username, "' "
"and jid='", SJID, "'"]) of
case odbc_queries:get_roster_by_jid(LServer, Username, SJID) of
{selected,
["username", "jid", "nick", "subscription", "ask",
"server", "subscribe", "type"],
"askmessage", "server", "subscribe", "type"],
[I]} ->
R = raw_to_record(I),
R = raw_to_record(LServer, I),
Groups =
case ejabberd_odbc:sql_query_t(
["select grp from rostergroups "
"where username='", Username, "' "
"and jid='", SJID, "'"]) of
case odbc_queries:get_roster_groups(LServer, Username, SJID) of
{selected, ["grp"], JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ ->
@@ -485,9 +431,10 @@ process_subscription(Direction, User, Server, JID1, Type) ->
R#roster{groups = Groups};
{selected,
["username", "jid", "nick", "subscription", "ask",
"server", "subscribe", "type"],
"askmessage", "server", "subscribe", "type"],
[]} ->
#roster{user = LUser,
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = LJID}
end,
NewState = case Direction of
@@ -508,26 +455,24 @@ process_subscription(Direction, User, Server, JID1, Type) ->
Item#roster.ask,
Type)
end,
AskMessage = case NewState of
{_, both} -> Reason;
{_, in} -> Reason;
_ -> ""
end,
case NewState of
none ->
{none, AutoReply};
{Subscription, Pending} ->
NewItem = Item#roster{subscription = Subscription,
ask = Pending},
ask = Pending,
askmessage = AskMessage},
ItemVals = record_to_string(NewItem),
ejabberd_odbc:sql_query_t(
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"insert into rosterusers("
" username, jid, nick, "
" subscription, ask, "
" server, subscribe, type) "
" values ", ItemVals]),
odbc_queries:roster_subscribe(LServer, Username, SJID, ItemVals),
{{push, NewItem}, AutoReply}
end
end,
case ejabberd_odbc:sql_transaction(LServer, F) of
case odbc_queries:sql_transaction(LServer, F) of
{atomic, {Push, AutoReply}} ->
case AutoReply of
none ->
@@ -605,7 +550,7 @@ out_state_change(none, none, subscribe) -> {none, out};
out_state_change(none, none, subscribed) -> none;
out_state_change(none, none, unsubscribe) -> none;
out_state_change(none, none, unsubscribed) -> none;
out_state_change(none, out, subscribe) -> none;
out_state_change(none, out, subscribe) -> {none, out}; %% We need to resend query (RFC3921, section 9.2)
out_state_change(none, out, subscribed) -> none;
out_state_change(none, out, unsubscribe) -> {none, none};
out_state_change(none, out, unsubscribed) -> none;
@@ -654,15 +599,7 @@ remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_transaction(
LServer,
fun() ->
ejabberd_odbc:sql_query_t(
["delete from rosterusers "
" where username='", Username, "';"
"delete from rostergroups "
" where username='", Username, "'"])
end),
odbc_queries:del_user_roster_t(LServer, Username),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -671,13 +608,11 @@ set_items(User, Server, SubEl) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
catch ejabberd_odbc:sql_query(
catch odbc_queries:transaction(
LServer,
["begin;",
lists:map(fun(El) ->
process_item_set_t(LUser, LServer, El)
end, Els),
"commit"]).
end, Els)).
process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
@@ -689,36 +624,18 @@ process_item_set_t(LUser, LServer, {xmlelement, _Name, Attrs, Els}) ->
LJID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
Item = #roster{user = LUser,
Item = #roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = LJID},
Item1 = process_item_attrs_ws(Item, Attrs),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove ->
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"delete from rostergroups "
" where username='", Username, "' "
" and jid='", SJID, "';"];
odbc_queries:del_roster_sql(Username, SJID);
_ ->
ItemVals = record_to_string(Item1),
ItemGroups = groups_to_string(Item2),
["delete from rosterusers "
" where username='", Username, "' "
" and jid='", SJID, "';"
"insert into rosterusers("
" username, jid, nick, "
" subscription, ask, "
" server, subscribe, type) "
" values ", ItemVals, ";"
"delete from rostergroups "
" where username='", Username, "' "
" and jid='", SJID, "';",
[["insert into rostergroups("
" username, jid, grp) "
" values ", ItemGroup, ";"] ||
ItemGroup <- ItemGroups]]
odbc_queries:update_roster_sql(Username, SJID, ItemVals, ItemGroups)
end
end;
process_item_set_t(_LUser, _LServer, _) ->
@@ -731,7 +648,7 @@ process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
error ->
process_item_attrs_ws(Item, Attrs);
JID1 ->
JID = {JID1#jid.user, JID1#jid.server, JID1#jid.resource},
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
end;
"name" ->
@@ -764,6 +681,39 @@ process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
process_item_attrs_ws(Item, []) ->
Item.
get_in_pending_subscriptions(Ls, User, Server) ->
JID = jlib:make_jid(User, Server, ""),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_roster(LServer, Username) of
{selected, ["username", "jid", "nick", "subscription", "ask",
"askmessage", "server", "subscribe", "type"],
Items} when is_list(Items) ->
Ls ++ lists:map(
fun(R) ->
Message = R#roster.askmessage,
{xmlelement, "presence",
[{"from", jlib:jid_to_string(R#roster.jid)},
{"to", jlib:jid_to_string(JID)},
{"type", "subscribe"}],
[{xmlelement, "status", [],
[{xmlcdata, Message}]}]}
end,
lists:flatmap(
fun(I) ->
R = raw_to_record(LServer, I),
case R#roster.ask of
in -> [R];
both -> [R];
_ -> []
end
end,
Items));
_ ->
Ls
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -773,11 +723,7 @@ get_jid_info(_, User, Server, JID) ->
LJID = jlib:jid_tolower(JID),
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
case catch ejabberd_odbc:sql_query(
LServer,
["select subscription from rosterusers "
"where username='", Username, "' "
"and jid='", SJID, "'"]) of
case catch odbc_queries:get_subscription(LServer, Username, SJID) of
{selected, ["subscription"], [{SSubscription}]} ->
Subscription = case SSubscription of
"B" -> both;
@@ -785,11 +731,7 @@ get_jid_info(_, User, Server, JID) ->
"F" -> from;
_ -> none
end,
Groups = case catch ejabberd_odbc:sql_query(
LServer,
["select grp from rostergroups "
"where username='", Username, "' "
"and jid='", SJID, "'"]) of
Groups = case catch odbc_queries:get_roster_group_by_jid(LServer, Username, SJID) of
{selected, ["grp"], JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ ->
@@ -803,11 +745,7 @@ get_jid_info(_, User, Server, JID) ->
{none, []};
true ->
SRJID = ejabberd_odbc:escape(jlib:jid_to_string(LRJID)),
case catch ejabberd_odbc:sql_query(
LServer,
["select subscription from rosterusers "
"where username='", Username, "' "
"and jid='", SRJID, "'"]) of
case catch odbc_queries:get_subscription(LServer, Username, SRJID) of
{selected, ["subscription"], [{SSubscription}]} ->
Subscription = case SSubscription of
"B" -> both;
@@ -815,11 +753,7 @@ get_jid_info(_, User, Server, JID) ->
"F" -> from;
_ -> none
end,
Groups = case catch ejabberd_odbc:sql_query(
LServer,
["select grp from rostergroups "
"where username='", Username, "' "
"and jid='", SRJID, "'"]) of
Groups = case catch odbc_queries:get_rostergroup_by_jid(LServer, Username, SRJID) of
{selected, ["grp"], JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ ->
@@ -834,8 +768,8 @@ get_jid_info(_, User, Server, JID) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
raw_to_record({User, SJID, Nick, SSubscription, SAsk,
_SServer, _SSubscribe, _SType}) ->
raw_to_record(LServer, {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType}) ->
case jlib:string_to_jid(SJID) of
error ->
error;
@@ -855,18 +789,21 @@ raw_to_record({User, SJID, Nick, SSubscription, SAsk,
"I" -> in;
_ -> none
end,
#roster{user = User,
#roster{usj = {User, LServer, LJID},
us = {User, LServer},
jid = LJID,
name = Nick,
subscription = Subscription,
ask = Ask}
ask = Ask,
askmessage = SAskMessage}
end.
record_to_string(#roster{user = User,
record_to_string(#roster{us = {User, _Server},
jid = JID,
name = Name,
subscription = Subscription,
ask = Ask}) ->
ask = Ask,
askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
Nick = ejabberd_odbc:escape(Name),
@@ -884,21 +821,26 @@ record_to_string(#roster{user = User,
in -> "I";
none -> "N"
end,
["("
"'", Username, "',"
["'", Username, "',"
"'", SJID, "',"
"'", Nick, "',"
"'", SSubscription, "',"
"'", SAsk, "',"
"'N', '', 'item')"].
"'", AskMessage, "',"
"'N', '', 'item'"].
groups_to_string(#roster{user = User,
groups_to_string(#roster{us = {User, _Server},
jid = JID,
groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
[["("
"'", Username, "',"
"'", SJID, "',"
"'", ejabberd_odbc:escape(Group), "')"] || Group <- Groups].
%% Empty groups do not need to be converted to string to be inserted in
%% the database
lists:foldl(fun([], Acc) -> Acc;
(Group, Acc) ->
String = ["'", Username, "',"
"'", SJID, "',"
"'", ejabberd_odbc:escape(Group), "'"],
[String|Acc] end, [], Groups).
+4 -5
View File
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : mod_service_log.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Manage announce messages
%%% Purpose : Copy user messages to logger service
%%% Created : 24 Aug 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
@@ -34,14 +34,13 @@ stop(Host) ->
ok.
log_user_send(From, To, Packet) ->
log_packet(From, To, Packet).
log_packet(From, To, Packet, From#jid.lserver).
log_user_receive(_JID, From, To, Packet) ->
log_packet(From, To, Packet).
log_packet(From, To, Packet, To#jid.lserver).
log_packet(From, To, {xmlelement, Name, Attrs, Els}) ->
Host = From#jid.lserver,
log_packet(From, To, {xmlelement, Name, Attrs, Els}, Host) ->
Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers, []),
ServerJID = #jid{user = "", server = Host, resource = "",
luser = "", lserver = Host, lresource = ""},
+59 -10
View File
@@ -8,7 +8,6 @@
-module(mod_shared_roster).
-author('alexey@sevcom.net').
-vsn('$Revision: 24 $ ').
-behaviour(gen_mod).
@@ -16,7 +15,8 @@
get_user_roster/2,
get_subscription_lists/3,
get_jid_info/4,
in_subscription/5,
process_item/2,
in_subscription/6,
out_subscription/4,
list_groups/1,
create_group/2,
@@ -54,7 +54,9 @@ start(Host, _Opts) ->
ejabberd_hooks:add(roster_get_subscription_lists, Host,
?MODULE, get_subscription_lists, 70),
ejabberd_hooks:add(roster_get_jid_info, Host,
?MODULE, get_jid_info, 70).
?MODULE, get_jid_info, 70),
ejabberd_hooks:add(roster_process_item, Host,
?MODULE, process_item, 50).
%ejabberd_hooks:add(remove_user, Host,
% ?MODULE, remove_user, 50),
@@ -68,7 +70,9 @@ stop(Host) ->
ejabberd_hooks:delete(roster_get_subscription_lists, Host,
?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info, Host,
?MODULE, get_jid_info, 70).
?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
?MODULE, process_item, 50).
%ejabberd_hooks:delete(remove_user, Host,
% ?MODULE, remove_user, 50),
@@ -76,14 +80,22 @@ stop(Host) ->
get_user_roster(Items, US) ->
{U, S} = US,
DisplayedGroups = get_user_displayed_groups(US),
%% Get shared roster users in all groups and remove self:
SRUsers =
lists:foldl(
fun(Group, Acc1) ->
lists:foldl(
fun(User, Acc2) ->
dict:append(User, get_group_name(S, Group), Acc2)
if User == US -> Acc2;
true -> dict:append(User,
get_group_name(S, Group),
Acc2)
end
end, Acc1, get_group_users(S, Group))
end, dict:new(), DisplayedGroups),
%% If partially subscribed users are also in shared roster, show them as
%% totally subscribed:
{NewItems1, SRUsersRest} =
lists:mapfoldl(
fun(Item, SRUsers1) ->
@@ -97,6 +109,8 @@ get_user_roster(Items, US) ->
{Item, SRUsers1}
end
end, SRUsers, Items),
%% Export items in roster format:
SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
us = US,
jid = {U1, S1, ""},
@@ -107,6 +121,28 @@ get_user_roster(Items, US) ->
{{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
%% This function in use to rewrite the roster entries when moving or renaming
%% them in the user contact list.
process_item(RosterItem, Host) ->
USFrom = RosterItem#roster.us,
{User,Server,_Resource} = RosterItem#roster.jid,
USTo = {User,Server},
DisplayedGroups = get_user_displayed_groups(USFrom),
CommonGroups = lists:filter(fun(Group) ->
is_user_in_group(USTo, Group, Host)
end, DisplayedGroups),
case CommonGroups of
[] -> RosterItem;
%% Roster item cannot be removed: We simply reset the original groups:
_ when RosterItem#roster.subscription == remove ->
GroupNames = lists:map(fun(Group) ->
get_group_name(Host, Group)
end, CommonGroups),
RosterItem#roster{subscription = both, ask = none,
groups=[GroupNames]};
_ -> RosterItem#roster{subscription = both, ask = none}
end.
get_subscription_lists({F, T}, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -148,7 +184,7 @@ get_jid_info({Subscription, Groups}, User, Server, JID) ->
{Subscription, Groups}
end.
in_subscription(Acc, User, Server, JID, Type) ->
in_subscription(Acc, User, Server, JID, Type, _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
out_subscription(User, Server, JID, Type) ->
@@ -198,8 +234,15 @@ create_group(Host, Group, Opts) ->
mnesia:transaction(F).
delete_group(Host, Group) ->
GroupHost = {Group, Host},
F = fun() ->
mnesia:delete({sr_group, {Group, Host}})
%% Delete the group ...
mnesia:delete({sr_group, GroupHost}),
%% ... and its users
Users = mnesia:index_read(sr_user, GroupHost, #sr_user.group_host),
lists:foreach(fun(UserEntry) ->
mnesia:delete_object(UserEntry)
end, Users)
end,
mnesia:transaction(F).
@@ -218,8 +261,6 @@ set_group_opts(Host, Group, Opts) ->
end,
mnesia:transaction(F).
get_user_groups(US) ->
Host = element(2, US),
case catch mnesia:dirty_read(sr_user, US) of
@@ -290,6 +331,13 @@ get_user_displayed_groups(US) ->
end, get_user_groups(US))),
[Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
is_user_in_group(US, Group, Host) ->
case catch mnesia:dirty_match_object(
#sr_user{us=US, group_host={Group, Host}}) of
[] -> false;
_ -> true
end.
add_user_to_group(Host, US, Group) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun() ->
@@ -298,7 +346,8 @@ add_user_to_group(Host, US, Group) ->
mnesia:transaction(F).
remove_user_from_group(Host, US, Group) ->
R = #sr_user{us = US, group_host = {Group, Host}},
GroupHost = {Group, Host},
R = #sr_user{us = US, group_host = GroupHost},
F = fun() ->
mnesia:delete_object(R)
end,
+6 -6
View File
@@ -147,7 +147,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
translate:translate(
Lang,
"Erlang Jabber Server\n"
"Copyright (c) 2002-2005 Alexey Shchepin")}]},
"Copyright (c) 2002-2006 Alexey Shchepin")}]},
{xmlelement, "BDAY", [],
[{xmlcdata, "2002-11-16"}]}
]}]}
@@ -281,7 +281,7 @@ set_vcard(User, LServer, VCARD) ->
?TLFIELD("text-single", "Birthday", "bday"),
?TLFIELD("text-single", "Country", "ctry"),
?TLFIELD("text-single", "City", "locality"),
?TLFIELD("text-single", "email", "email"),
?TLFIELD("text-single", "Email", "email"),
?TLFIELD("text-single", "Organization Name", "orgname"),
?TLFIELD("text-single", "Organization Unit", "orgunit")
]}]).
@@ -416,7 +416,7 @@ iq_get_vcard(Lang) ->
[{xmlcdata, translate:translate(
Lang,
"ejabberd vCard module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
find_xdata_el1(SubEls).
@@ -439,10 +439,10 @@ find_xdata_el1([_ | Els]) ->
search_result(Lang, JID, ServerHost, Data) ->
[{xmlelement, "title", [],
[{xmlcdata, translate:translate(Lang, "Results of search in ") ++
[{xmlcdata, translate:translate(Lang, "Search Results for ") ++
jlib:jid_to_string(JID)}]},
{xmlelement, "reported", [],
[?LFIELD("JID", "jid"),
[?LFIELD("Jabber ID", "jid"),
?LFIELD("Full Name", "fn"),
?LFIELD("Name", "given"),
?LFIELD("Middle Name", "middle"),
@@ -451,7 +451,7 @@ search_result(Lang, JID, ServerHost, Data) ->
?LFIELD("Birthday", "bday"),
?LFIELD("Country", "ctry"),
?LFIELD("City", "locality"),
?LFIELD("email", "email"),
?LFIELD("Email", "email"),
?LFIELD("Organization Name", "orgname"),
?LFIELD("Organization Unit", "orgunit")
]}] ++ lists:map(fun record_to_item/1, search(ServerHost, Data)).
+555 -343
View File
File diff suppressed because it is too large Load Diff
+35 -50
View File
@@ -111,7 +111,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
translate:translate(
Lang,
"Erlang Jabber Server\n"
"Copyright (c) 2002-2005 Alexey Shchepin")}]},
"Copyright (c) 2002-2006 Alexey Shchepin")}]},
{xmlelement, "BDAY", [],
[{xmlcdata, "2002-11-16"}]}
]}]}
@@ -131,12 +131,11 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
end;
get ->
#jid{luser = LUser, lserver = LServer} = To,
US = {LUser, LServer},
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
["select vcard from vcard "
"where username='", Username, "'"]) of
"where username='", Username, "';"]) of
{selected, ["vcard"], [{SVCARD}]} ->
case xml_stream:parse_element(SVCARD) of
{error, _Reason} ->
@@ -186,8 +185,6 @@ set_vcard(User, LServer, VCARD) ->
LOrgName = stringprep:tolower(OrgName),
LOrgUnit = stringprep:tolower(OrgUnit),
US = {LUser, LServer},
if
(LUser == error) or
(LFN == error) or
@@ -231,32 +228,30 @@ set_vcard(User, LServer, VCARD) ->
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
ejabberd_odbc:sql_query(
ejabberd_odbc:sql_transaction(
LServer,
["begin;"
"delete from vcard where username='", LUsername, "';"
"insert into vcard(username, vcard) "
"values ('", LUsername, "', '", SVCARD, "');"
"delete from vcard_search where lusername='", LUsername, "';"
"insert into vcard_search("
" username, lusername, fn, lfn, family, lfamily,"
" given, lgiven, middle, lmiddle, nickname, lnickname,"
" bday, lbday, ctry, lctry, locality, llocality,"
" email, lemail, orgname, lorgname, orgunit, lorgunit)"
"values (",
" '", Username, "', '", LUsername, "',"
" '", SFN, "', '", SLFN, "',"
" '", SFamily, "', '", SLFamily, "',"
" '", SGiven, "', '", SLGiven, "',"
" '", SMiddle, "', '", SLMiddle, "',"
" '", SNickname, "', '", SLNickname, "',"
" '", SBDay, "', '", SLBDay, "',"
" '", SCTRY, "', '", SLCTRY, "',"
" '", SLocality, "', '", SLLocality, "',"
" '", SEMail, "', '", SLEMail, "',"
" '", SOrgName, "', '", SLOrgName, "',"
" '", SOrgUnit, "', '", SLOrgUnit, "');"
"commit"])
[["delete from vcard where username='", LUsername, "';"],
["insert into vcard(username, vcard) "
"values ('", LUsername, "', '", SVCARD, "');"],
["delete from vcard_search where lusername='", LUsername, "';"],
["insert into vcard_search("
" username, lusername, fn, lfn, family, lfamily,"
" given, lgiven, middle, lmiddle, nickname, lnickname,"
" bday, lbday, ctry, lctry, locality, llocality,"
" email, lemail, orgname, lorgname, orgunit, lorgunit)"
"values (",
" '", Username, "', '", LUsername, "',"
" '", SFN, "', '", SLFN, "',"
" '", SFamily, "', '", SLFamily, "',"
" '", SGiven, "', '", SLGiven, "',"
" '", SMiddle, "', '", SLMiddle, "',"
" '", SNickname, "', '", SLNickname, "',"
" '", SBDay, "', '", SLBDay, "',"
" '", SCTRY, "', '", SLCTRY, "',"
" '", SLocality, "', '", SLLocality, "',"
" '", SEMail, "', '", SLEMail, "',"
" '", SOrgName, "', '", SLOrgName, "',"
" '", SOrgUnit, "', '", SLOrgUnit, "');"]])
end.
-define(TLFIELD(Type, Label, Var),
@@ -286,14 +281,11 @@ set_vcard(User, LServer, VCARD) ->
?TLFIELD("text-single", "Birthday", "bday"),
?TLFIELD("text-single", "Country", "ctry"),
?TLFIELD("text-single", "City", "locality"),
?TLFIELD("text-single", "email", "email"),
?TLFIELD("text-single", "Email", "email"),
?TLFIELD("text-single", "Organization Name", "orgname"),
?TLFIELD("text-single", "Organization Unit", "orgunit")
]}]).
do_route(ServerHost, From, To, Packet) ->
#jid{user = User, resource = Resource} = To,
if
@@ -421,7 +413,7 @@ iq_get_vcard(Lang) ->
[{xmlcdata, translate:translate(
Lang,
"ejabberd vCard module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
"Copyright (c) 2003-2006 Alexey Shchepin")}]}].
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
find_xdata_el1(SubEls).
@@ -444,10 +436,10 @@ find_xdata_el1([_ | Els]) ->
search_result(Lang, JID, ServerHost, Data) ->
[{xmlelement, "title", [],
[{xmlcdata, translate:translate(Lang, "Results of search in ") ++
[{xmlcdata, translate:translate(Lang, "Search Results for ") ++
jlib:jid_to_string(JID)}]},
{xmlelement, "reported", [],
[?LFIELD("JID", "jid"),
[?LFIELD("Jabber ID", "jid"),
?LFIELD("Full Name", "fn"),
?LFIELD("Name", "given"),
?LFIELD("Middle Name", "middle"),
@@ -456,7 +448,7 @@ search_result(Lang, JID, ServerHost, Data) ->
?LFIELD("Birthday", "bday"),
?LFIELD("Country", "ctry"),
?LFIELD("City", "locality"),
?LFIELD("email", "email"),
?LFIELD("Email", "email"),
?LFIELD("Organization Name", "orgname"),
?LFIELD("Organization Unit", "orgunit")
]}] ++ lists:map(fun(R) -> record_to_item(ServerHost, R) end,
@@ -514,7 +506,7 @@ search(LServer, Data) ->
["select username, fn, family, given, middle, "
" nickname, bday, ctry, locality, "
" email, orgname, orgunit from vcard_search ",
MatchSpec, Limit]) of
MatchSpec, Limit, ";"]) of
{selected, ["username", "fn", "family", "given", "middle",
"nickname", "bday", "ctry", "locality",
"email", "orgname", "orgunit"],
@@ -564,12 +556,7 @@ make_val(Match, Field, Val) ->
case lists:suffix("*", Val) of
true ->
Val1 = lists:sublist(Val, length(Val) - 1),
Val2 = lists:flatten([case C of
$_ -> "\\_";
$% -> "\\%";
_ -> C
end || C <- Val1]),
SVal = ejabberd_odbc:escape(Val2 ++ "%"),
SVal = ejabberd_odbc:escape_like(Val1) ++ "%",
[Field, " LIKE '", SVal, "'"];
_ ->
SVal = ejabberd_odbc:escape(Val),
@@ -660,11 +647,9 @@ remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_query(
ejabberd_odbc:sql_transaction(
LServer,
["begin;"
"delete from vcard where username='", Username, "';"
"delete from vcard_search where lusername='", Username, "';"
"commit"]).
[["delete from vcard where username='", Username, "';"],
["delete from vcard_search where lusername='", Username, "';"]]).
+397
View File
@@ -0,0 +1,397 @@
% $Id$
% Czech translation
% Authors: Milos Svasek [DuxforD] from openheads.net
% jlib.hrl
{"No resource provided", "Nebyl poskytnutý žádny zdroj"}.
% mod_configure.erl
{"Choose storage type of tables", "Vyberte typ úložiště pro tabulky"}.
{"RAM copy", "Kopie RAM"}.
{"RAM and disc copy", "Kopie RAM a disku"}.
{"Disc only copy", "Jen kopie disku"}.
{"Remote copy", "Vzdálená kopie"}.
{"Stop Modules at ", "Zastavit moduly na "}.
{"Choose modules to stop", "Vyberte moduly, které mají být zastavené"}.
{"Start Modules at ", "Spustit moduly na "}.
{"Enter list of {Module, [Options]}", "Vložte seznam modulů {Modul, [Parametre]}"}.
{"List of modules to start", "Seznam modulů, které mají být spuštěné"}.
{"Backup to File at ", "Záloha do souboru na "}.
{"Enter path to backup file", "Zadajte cestu k souboru se zálohou"}.
{"Path to File", "Cesta k souboru"}.
{"Restore Backup from File at ", "Obnovit zálohu ze souboru na "}.
{"Dump Backup to Text File at ", "Uložit zálohu do textového souboru na "}.
{"Enter path to text file", "Zadajte cestu k textovému souboru"}.
{"Import User from File at ", "Importovat uživatele ze souboru na "}.
{"Enter path to jabberd1.4 spool file", "Zadejte cestu k spool souboru jabberd1.4"}.
{"Import Users from Dir at ", "Importovat uživatele z adresáře na "}.
{"Enter path to jabberd1.4 spool dir", "Zadejte cestu k jabberd1.4 spool adresáři"}.
{"Path to Dir", "Cesta k adresáři"}.
{"Hostname Configuration", "Konfigurace jména serveru"}.
{"Choose host name", "Vyberte jméno serveru"}.
{"Host name", "Jméno serveru"}.
{"Access Control List Configuration", "Konfigurace seznamu přístupových práv (ACL)"}.
{"Access control lists", "Seznamy přístupových práv (ACL)"}.
{"Access Configuration", "Konfigurace přístupů"}.
{"Access rules", "Pravidla přístupů"}.
{"Remove Users", "Odstranit uživatele"}.
{"Choose users to remove", "Vybrat uživatele, kteří budou odstraněni"}.
{"Administration of ", "Administrace "}.
{"Action on user", "Akce aplikovaná na uživatele"}.
{"Edit Properties", "Upravit vlastnosti"}.
{"Remove User", "Odstranit uživatele"}.
% mod_disco.erl
{"Configuration", "Konfigurace"}.
{"Online Users", "Online uživatelé"}.
{"All Users", "Všichni uživatelé"}.
{"To ~s", "Pre ~s"}.
{"From ~s", "Od ~s"}.
{"Running Nodes", "Bežící uzly"}.
{"Stopped Nodes", "Zastavené uzly"}.
{"Host Name", "Jméno serveru"}.
{"Access Control Lists", "Seznamy přístupových práv (ACL)"}.
{"Access Rules", "Pravidla přístupů"}.
{"Remove Users", "Odstranit uživatele"}.
{"Modules", "Moduly"}.
{"Start Modules", "Spustit moduly"}.
{"Stop Modules", "Zastavit moduly"}.
{"Backup Management", "Správa zálohování"}.
{"Backup", "Zálohovat"}.
{"Restore", "Obnovit"}.
{"Dump to Text File", "Uložit do textového souboru"}.
{"Import File", "Import souboru"}.
{"Import Directory", "Import adresáře"}.
% mod_register.erl
{"Choose a username and password to register with this server",
"Zadejte jméno uživatele a heslo pro registraci na tomto serveru"}.
% mod_vcard.erl
{"You need an x:data capable client to search",
"K vyhledávání potřebujete klienta podporujícího x:data"}.
{"Search users in ", "Hledat uživatele v "}.
{"Fill in fields to search for any matching Jabber User",
"Vyplňte políčka pro vyhledávání Jabber uživatele"}.
{"User", "Uživatel: "}.
{"Full Name", "Celé jméno: "}.
{"Name", "Jméno: "}.
{"Middle Name", "Prostřední jméno: "}.
{"Family Name", "Příjmení: "}.
{"Nickname", "Přezdívka: "}.
{"Birthday", "Datum narození: "}.
{"Country", "Země: "}.
{"City", "Město: "}.
{"Organization Name", "Název firmy: "}.
{"Organization Unit", "Oddělení: "}.
% mod_muc/mod_muc.erl
{"You need an x:data capable client to register nickname",
"K registraci přezdívky potřebujete klienta podporujícího z x:data"}.
{"Nickname Registration at ", "Registrace prezdívky na "}.
{"Enter nickname you want to register", "Zadajte prezdívku, kterou chete zaregistrovat"}.
{"Only service administrators are allowed to send service messages",
"Pouze správci služby mají povolené odesílání servisních zpráv"}.
{"Conference room does not exist", "Konferenční místnost neexistuje"}.
{"Access denied by service policy", "Přístup byl zamítnutý nastavením služby"}.
{"Specified nickname is already registered", "Zadaná prezdívka je již registrována"}.
% mod_muc/mod_muc_room.erl
{" has set the subject to: ", "změnil(a) téma na: "}.
{"You need an x:data capable client to configure room",
"Ke konfiguraci místnosti potřebujete klienta podporujícího z x:data"}.
{"Configuration for ", "Konfigurace pre "}.
{"Room title", "Název místnosti"}.
{"Password", "Heslo"}.
{"Only moderators and participants are allowed to change subject in this room",
"Jen moderátoři a účastníci mají povoleno měnit téma této místnosti"}.
{"Only moderators are allowed to change subject in this room",
"Jen moderátoři mají povoleno měnit téma místnosti"}.
{"Visitors are not allowed to send messages to all occupants",
"Návštevníci nemají povoleno zsílat zprávy všem přihlášeným do konference"}.
{"Only occupants are allowed to send messages to the conference",
"Jen členové mají povolené zasílat správy do konference"}.
{"It is not allowed to send normal messages to the conference",
"Není povoleno odesílat normální zprávy do konference"}.
{"It is not allowed to send private messages to the conference",
"Není povoleno odesílat soukromé zprávy do konference"}.
{"Improper message type", "Nesprávný typ zprávy"}.
{"Nickname is already in use by another occupant", "Prezdívka je již používána jiným členem"}.
{"Nickname is registered by another person", "Prezdívka je registrována jinou osobou"}.
{"It is not allowed to send private messages of type \"groupchat\"",
"Není dovoleno odeslání soukromé zprávy typu \"Skupinová zpráva\" "}.
{"Recipient is not in the conference room", "Příjemce se nenachází v konferenční místnosti"}.
{"Only occupants are allowed to send queries to the conference",
"Jen členové mohou odesílat požadavky (query) do konference"}.
{"Queries to the conference members are not allowed in this room",
"Požadavky (queries) na členy konference nejsou v této místnosti povolené"}.
{"You have been banned from this room", "Byl jste vyloučen z této místnosti"}.
{"Membership required to enter this room", "Pro vstup do místnosti musíte být členem"}.
{"Password required to enter this room", "Pro vstup do místnosti musíte zadat heslo"}.
{"Incorrect password", "Nesprávne heslo"}.
{"Administrator privileges required", "Jsou potřebná práva administrátora"}.
{"Moderator privileges required", "Jsou potřebná práva moderátora"}.
{"JID ~s is invalid", "JID ~s je neplatné"}.
{"Nickname ~s does not exist in the room", "Prezdívka ~s v místnosti neexistuje"}.
{"Invalid affiliation: ~s", "Neplatné přiřazení: ~s"}.
{"Invalid role: ~s", "Neplatná role: ~s"}.
{"Owner privileges required", "Jsou vyžadována práva vlastníka"}.
{"private, ", "soukromá, "}.
{"Present real JIDs to", "Zjisti skutečné JIDy pro"}.
{"moderators only", "pouze pro moderátory"}.
{"anyone", "každý"}.
% mod_irc/mod_irc.erl
{"You need an x:data capable client to configure mod_irc settings",
"Pro konfiguraci mod_irc potřebujete klienta podporujícího x:data"}.
{"Registration in mod_irc for ", "Registrace do mod_irc na "}.
{"Enter username and encodings you wish to use for connecting to IRC servers",
"Vložte jméno uživatele a kódování, které chcete používat při připojení na IRC server"}.
{"IRC Username", "IRC prezdívka"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\"}, {\"vendetta.fef.net\", \"iso8859-1\"}].",
"Příklad: [{\"irc.freenode.net\",\"utf-8\"}, {\irc.freenode.net\", \"iso8859-2\"}]."}.
{"Encodings", "Kódování"}.
% web/ejabberd_web_admin.erl
{"Users", "Uživatelé"}.
{"Nodes", "Uzly"}.
{"Statistics", "Statistiky"}.
{"Submitted", "Odeslané"}.
{"CPU Time:", "Čas procesoru"}.
{"Delete Selected", "Smazat vybrané"}.
{"Submit", "Odeslat"}.
{"~s access rule configuration", "~s konfigurace pravidla přístupu"}.
{"Node not found", "Uzel nenalezen"}.
{"Add New", "Přidat nový"}.
{"Change Password", "Změnit heslo"}.
{"Connected Resources:", "Připojené zdroje:"}.
{"Password:", "Heslo:"}.
{"None", "Nic"}.
{"Node ", "Uzel "}.
{"Restart", "Restart"}.
{"Stop", "Stop"}.
{"Name", "Jméno"}.
{"Storage Type", "Typ úložiště"}.
{"Size", "Velikost"}.
{"Memory", "Paměť"}.
{"OK", "OK"}.
{"Listened Ports at ", "Otevřené porty na "}.
{"Port", "Port"}.
{"Module", "Modul"}.
{"Options", "Nastavení"}.
{"Update", "Aktualizovat"}.
{"Delete", "Smazat"}.
{"Add User", "Přidat uživatele"}.
{"Last Activity", "Poslední aktivita"}.
{"Never", "Nikdy"}.
{"Time", "Čas"}.
{"From", "Od"}.
{"To", "Pro"}.
{"Packet", "Paket"}.
{"Roster", "Seznam kontaktů"}.
{"Nickname", "Prezdívka"}.
{"Subscription", "Přihlášení"}.
{"Pending", "Čekající"}.
{"Groups", "Skupiny"}.
{"Remove", "Odstranit"}.
{"User ", "Uživatel "}.
{"Roster of ", "Seznam kontaktů "}.
{"Shared Roster", "Sdílený seznam kontaktů"}.
{"Online", "Online"}.
{"Validate", "Ověřit"}.
{"Name:", "Jméno:"}.
{"Description:", "Popis:"}.
{"Members:", "Členové:"}.
{"Displayed Groups:", "Zobrazené skupiny:"}.
{"Group ", "Skupina "}.
{"Period: ", "Čas:"}.
{"Last month", "Poslední měsíc"}.
{"Last year", "Poslední rok"}.
{"All activity", "Všechny aktivity"}.
{"Show Ordinary Table", "Zobrazit bežnou tabulku"}.
{"Show Integral Table", "Zobrazit kompletní tabulku"}.
{"Start", "Start"}.
{"Modules at ", "Moduly na "}.
{"Virtual Hosts", "Virtuální servery"}.
{"ejabberd virtual hosts", "Ejabberd virtuální servery"}.
{"Host", "Server"}.
{"ejabberd Web Interface", "Ejabberd Web rozhraní"}.
% mod_vcard_odbc.erl
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)",
"Pro vyhledání Jabber uživatele vyplňte formulář (přidejte znak * na konec, pro vyhledání podřetězce)"}.
% ejabberd_c2s.erl
{"Use of STARTTLS required", "Je vyžadováno STARTTLS."}.
{"Replaced by new connection", "Nahrazeno novým spojením"}.
% mod_pubsub/mod_pubsub.erl
{[], " "}.
{"Node Creator", "Majitel uzlu"}.
{"Deliver payloads with event notifications", "Doručovat náklad s upozorněním na událost"}.
{"Notify subscribers when the node configuration changes", "Upozornit členy na změnu nastavení uzlu"}.
{"Notify subscribers when the node is deleted", "Upozornit členy na smazaní uzlu"}.
{"Notify subscribers when items are removed from the node", "Upozornit členy na odstranění položek z uzlu"}.
{"Persist items to storage", "Uložit položky natrvalo do úložiště"}.
{"Max # of items to persist", "Maximální počet položek, které je možné natrvalo uložit"}.
{"Whether to allow subscriptions", "Povolit přihlašování"}.
{"Specify the subscriber model", "Specifikovat přihlašovací model"}.
{"Specify the publisher model", "Specifikovat model pro publikování"}.
{"Max payload size in bytes", "Maximální náklad v bajtech"}.
{"Send items to new subscribers", "Odeslat položky novým uživatelům"}.
{"Only deliver notifications to available users", "Doručovat upozornení jen aktuálně přihlášeným uživatelům"}.
{"Specify the current subscription approver", "Zadat současného schvalovatele přihlášení "}.
% mod_irc/mod_irc.erl
{"If you want to specify different encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\"}'. By default this service use \"~s\" encoding.",
"Pokud chcete zadat jiné kódování pro IRC servery, vyplňte seznam s hodnotami ve formátě '{\"irc server\",\"encoding\"}'. Předvolené kódování pro tuto službu je \"~s\"."}.
% mod_muc/mod_muc.erl
{"Room creation is denied by service policy", "Vytváranie miestnosti nie je povolené"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_vcard_odbc.erl
{"Erlang Jabber Server\nCopyright (c) 2002-2006 Alexey Shchepin",
"Erlang Jabber Server\nCopyright (c) 2002-2006 Alexey Shchepin"}.
{"Email", "E-mail"}.
{"ejabberd vCard module\nCopyright (c) 2003-2006 Alexey Shchepin",
"Ejabberd vCard modul\nCopyright (c) 2003-2006 Alexey Shchepin"}.
{"Search Results for ", "Hledat výsledky pro "}.
{"Jabber ID", "Jabber ID"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_adhoc.erl
{"Commands", "Příkazy"}.
{"Ping", "Ping"}.
{"Pong", "Pong"}.
% /usr/home/src/ejabberd/ejabberd/src/ejabberd_c2s.erl
{"Replaced by new connection", "Nahrazeno novým spojením"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_announce.erl
{"Really delete message of the day?", "Skutečně smazat správu dne?"}.
{"Subject", "Předmět"}.
{"Message body", "Tělo zprávy"}.
{"No body provided for announce message", "Zpráva neobsahuje text"}.
{"Announcements", "Oznámení"}.
{"Send announcement to all users", "Odeslat seznam všech uživatelů"}.
{"Send announcement to all online users", "Odeslat seznam všech online uživatelů"}.
{"Send announcement to all online users on all hosts", "Odeslat seznam všech online uživatelů na všech serverech"}.
{"Set message of the day and send to online users", "Nastavit zprávu dne a odeslat jí online uživatelům"}.
{"Update message of the day (don't send)", "Aktualizovat zprávu dne (neodesíelat)"}.
{"Delete message of the day", "Smazat zprávu dne"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_configure.erl
{"Database", "Databáze"}.
{"Outgoing s2s Connections", "Odchozí s2s spojení"}.
{"Import Users From jabberd 1.4 Spool Files", "Importovat uživatelů z jabber 1.4 spool souborů"}.
{"Database Tables Configuration at ", "Konfigurace databázových tabulek "}.
% /usr/home/src/ejabberd/ejabberd/src/mod_pubsub/mod_pubsub.erl
{"ejabberd pub/sub module\nCopyright (c) 2003-2006 Alexey Shchepin",
"ejabberd pub/sub modul\nCopyright (c) 2003-2006 Alexey Shchepin"}.
% /usr/home/src/ejabberd/ejabberd/src/web/ejabberd_web_admin.erl
{"ejabberd Web Interface", "Ejabberd Web rozhraní"}.
{"Administration", "Administrace"}.
{"ejabberd (c) 2002-2006 Alexey Shchepin, 2004-2006 Process One",
"Ejabberd (c) 2002-2006 Alexey Shchepin, 2004-2006 Process One"}.
{"(Raw)", "(Zdroj)"}.
{"Bad format", "Nesprávný formát"}.
{"Raw", "Zdroj"}.
{"Users Last Activity", "Poslední aktivita uživatele"}.
{"Registered Users", "Registrovaní uživatelé"}.
{"Offline Messages", "Offline zprávy"}.
{"Registered Users:", "Registrovaní živatelé:"}.
{"Authenticated Users:", "Ověření uživatelé:"}.
{"Online Users:", "Online uživatelé:"}.
{"Outgoing s2s Connections:", "Odchozí s2s spojení:"}.
{"Outgoing s2s Servers:", "Odchozí s2s servery:"}.
{"Offline Messages:", "Offline zprávy"}.
{"~s's Offline Messages Queue", "~s Offline zpráv"}.
{"Add Jabber ID", "Přidat JID"}.
{"No Data", "Žádná data"}.
{"Listened Ports", "Otevřené porty"}.
{"RPC Call Error", "Chyba RPC volání"}.
{"Database Tables at ", "Databázové tabulky na "}.
{"Backup of ", "Záloha na "}.
{"Remark that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.",
"Podotýkáme, že tato nastavení budou zálohované do zabudované databáze Mnesia. Pokud používáte ODBC modul, musíte zálohovat vaší SQL databázi separátně."}.
{"Store binary backup:", "Uložit binární zálohu:"}.
{"Restore binary backup immediately:", "Okamžitě obnovit binární zálohu:"}.
{"Restore binary backup after next ejabberd restart (requires less memory):",
"Obnovit binární zálohu při následujícím restartu Ejabberd (vyžaduje méně paměti)"}.
{"Store plain text backup:", "Uložit zálohu do textového souboru:"}.
{"Restore plain text backup immediately:", "Okamžitě obnovit zálohu z textového souboru:"}.
{"Statistics of ~p", "Statistiky ~p"}.
{"Uptime:", "Čas běhu"}.
{"Transactions Commited:", "Transakce potvrzena"}.
{"Transactions Aborted:", "Transakce zrušena"}.
{"Transactions Restarted:", "Transakce restartována"}.
{"Transactions Logged:", "Transakce zaznamenána"}.
{"Update ", "Aktualizovat "}.
{"Update plan", "Aktualizovat plán"}.
{"Updated modules", "Aktualizované moduly"}.
{"Update script", "Aktualizované skripty"}.
{"Low level update script", "Nízkoúrovňový aktualizační skript"}.
{"Script check", "Kontrola skriptu"}.
{"Not Found", "Nenalezeno"}.
{"Shared Roster Groups", "Skupiny pro zdílený seznam kontaktů"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_irc/mod_irc.erl
{"ejabberd IRC module\nCopyright (c) 2003-2006 Alexey Shchepin", "Ejabberd IRC modul\nCopyright (c) 2003-2006 Alexey Shchepin"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_muc/mod_muc_log.erl
{"Chatroom configuration modified", "Nastavení diskuzní místnosti bylo změněno"}.
{"joins the room", "vstoupil(a) do místnosti"}.
{"leaves the room", "opustil(a) místnost"}.
{"has been kicked", "byl(a) vyhozen(á) z místnosti"}.
{"has been banned", "byl(a) zablokován(a)"}.
{"is now known as", "se přejmenoval(a) na"}.
{"Monday", "Pondělí"}.
{"Tuesday", "Úterý"}.
{"Wednesday", "Středa"}.
{"Thursday", "Čtvrtek"}.
{"Friday", "Pátek"}.
{"Saturday", "Sobota"}.
{"Sunday", "Neděle"}.
{"January", "Leden"}.
{"February", "Únor"}.
{"March", "Březen"}.
{"April", "Duben"}.
{"May", "Květen"}.
{"June", "Červen"}.
{"July", "Červenec"}.
{"August", "Srpen"}.
{"September", "Září"}.
{"October", "říjen"}.
{"November", "Listopad"}.
{"December", "Prosinec"}.
{"Room Configuration", "Nastavení místnosti"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_muc/mod_muc.erl
{"You must fill in field \"Nickname\" in the form",
"Musíte vyplnit políčko \"Prezdívka\" ve formuláři"}.
{"ejabberd MUC module\nCopyright (c) 2003-2006 Alexey Shchepin",
"Ejabberd MUC modul\nCopyright (c) 2003-2006 Alexey Shchepin"}.
% /usr/home/src/ejabberd/ejabberd/src/mod_muc/mod_muc_room.erl
{"This room is not anonymous", "Tato místnost není anonymní"}.
{"Make room persistent", "Nastavit místnost jako stálou"}.
{"Make room public searchable", "Nastavit místnost jako veřejně prohldávatelnou"}.
{"Make participants list public", "Nastavit seznam účastníků jako veřejný"}.
{"Make room password protected", "Chránit místnost heslem"}.
{"Make room members-only", "Nastavit místnost jen pro členy"}.
{"Make room moderated", "Nastavit místnost jako moderovanou"}.
{"Default users as participants", "Uživatelé jsou implicitně členy"}.
{"Allow users to change subject", "Povolit uživatelům měnit téma této místnosti"}.
{"Allow users to send private messages", "Povolit uživatelům odesíelat soukromé zprávy"}.
{"Allow users to query other users", "Povolit uživatelům odesíela požadavky (query) ostatním uživatelům"}.
{"Allow users to send invites", "povolit uživatelům posíelání pozvánek"}.
{"Enable logging", "Zapnout ukládání historie"}.
{"Description", "Popis"}.
{"Number of occupants", "Počet účastníků"}.
% Local Variables:
% mode: erlang
% End:
+132 -80
View File
@@ -38,27 +38,23 @@
{"Action on user", "Aktion auf Benutzer"}.
{"Edit Properties", "Einstellungen ändern"}.
{"Remove User", "Benutzer löschen"}.
{"DB Tables Configuration at ", "DB Tabellenkonfiguration auf "}.
% mod_disco.erl
{"Configuration", "Konfiguration"}.
{"Online Users", "Online Benutzer"}.
{"All Users", "Alle Benutzer"}.
{"Outgoing S2S connections", "Rausgehende S2S Verbindungen"}.
{"To ~s", "An ~s"}.
{"From ~s", "Vom ~s"}.
{"From ~s", "Von ~s"}.
{"Running Nodes", "Aktive Knoten"}.
{"Stopped Nodes", "Inaktive Knoten"}.
{"Host Name", "Hostname"}.
{"Access Control Lists", "Zugangskontrolllisten"}.
{"Access Rules", "Zugangsregeln"}.
{"Remove Users", "Benutzer löschen"}.
{"DB", "DB"}.
{"Modules", "Module"}.
{"Start Modules", "Module starten"}.
{"Stop Modules", "Module stoppen"}.
{"Backup Management", "Datensicherungsmanagement"}.
{"Import users from jabberd1.4 spool files", "Benutzer von jabberd1.4 spool Dateien importieren"}.
{"Backup", "Sicherung"}.
{"Restore", "Wiederherstellung"}.
{"Dump to Text File", "Ausgabe in Textdatei"}.
@@ -70,15 +66,14 @@
"Wählen Sie zum Registrieren einen Benutzernamen und ein Passwort"}.
% mod_vcard.erl
{"ejabberd vCard module\nCopyright (c) 2003-2005 Alexey Shchepin", "ejabberd vCard module\nCopyright (c) 2003-2005 Alexey Shchepin"}.
{"ejabberd vCard module\nCopyright (c) 2003-2006 Alexey Shchepin", "ejabberd vCard module\nCopyright (c) 2003-2006 Alexey Shchepin"}.
{"You need an x:data capable client to search",
"Sie brauchen einen x:data kompatiblen Client um zu suchen"}.
"Sie brauchen einen x:data kompatiblen Client um suchen zu können"}.
{"Search users in ", "Benutzer suchen in "}.
{"Fill in fields to search for any matching Jabber User",
"Felder ausfüllen, um nach passenden Jabber Benutzern zu suchen"}.
{"Results of search in ", "Ergebnisse der Suche in "}.
{"User", "User"}.
{"User", "Benutzer"}.
{"Full Name", "Ganzer Name"}.
{"Name", "Vorname"}.
{"Middle Name", "Mittelname"}.
@@ -87,12 +82,11 @@
{"Birthday", "Geburtsdatum"}.
{"Country", "Land"}.
{"City", "Stadt"}.
{"email", "E-Mail"}.
{"Organization Name", "Firmenname"}.
{"Organization Unit", "Organisationseinheit"}.
{"Organization Unit", "Abteilung"}.
% mod_pubsub/mod_pubsub.erl
{"ejabberd pub/sub module\nCopyright (c) 2003-2005 Alexey Shchepin", "ejabberd pub/sub module\nCopyright (c) 2003-2005 Alexey Shchepin"}.
{"ejabberd pub/sub module\nCopyright (c) 2003-2006 Alexey Shchepin", "ejabberd pub/sub module\nCopyright (c) 2003-2006 Alexey Shchepin"}.
{[], " "}.
{"Node Creator", "Knoten Erschaffer"}.
{"Deliver payloads with event notifications", "Nutzlast mit Benachrichtigungen versenden"}.
@@ -114,12 +108,12 @@
"Sie brauchen einen für x:data geeigneten Client, um sich mit dem Spitznamen zu registrieren"}.
{"Nickname Registration at ", "Registrieren des Spitznamens "}.
{"Enter nickname you want to register", "Geben Sie den zu registrierenden Spitznamen ein"}.
{"ejabberd MUC module\nCopyright (c) 2003-2005 Alexey Shchepin", "ejabberd MUC module\nCopyright (c) 2003-2005 Alexey Shchepin"}.
{"ejabberd MUC module\nCopyright (c) 2003-2006 Alexey Shchepin", "ejabberd MUC module\nCopyright (c) 2003-2006 Alexey Shchepin"}.
{"Only service administrators are allowed to send service messages",
"Nur Service Administratoren sind berechtigt, Servicenachrichten zu lesen"}.
"Nur Service Administratoren sind berechtigt, Servicenachrichten zu senden"}.
{"Conference room does not exist", "Konferenzraum existiert nicht"}.
{"Access denied by service policy", "Zutritt verweigert von der Service Policy"}.
{"You must fill in field \"nick\" in the form", "Sie müssen etwas ins Feld \"nick\" eintragen"}.
{"You must fill in field \"Nickname\" in the form", "Sie müssen etwas ins Feld \"Nickname\" eintragen"}.
{"Specified nickname is already registered", "Der angegebene Name ist bereits vergeben"}.
{"Room creation is denied by service policy", "Anlegen des Raumes aufgrund von Servereinstellungen verweigert"}.
@@ -129,25 +123,7 @@
"Sie brauchen einen für x:data geeigneten Client um den Raum zu konfigurieren"}.
{"Configuration for ", "Konfiguration für "}.
{"Room title", "Raumname"}.
{"Allow users to change subject?", "Benutzer dürfen das Thema ändern?"}.
{"Allow users to query other users?",
"Benutzer dürfen andere Benutzer anfragen(query)?"}.
{"Allow users to send private messages?",
"Benutzer dürfen private Nachrichten verschicken?"}.
{"Make room public searchable?", "Den Raum öffentlich dursuchbar machen?"}.
{"Make participants list public?", "Die Teilnehmerliste öffentlich machen?"}.
{"Make room persistent?", "Den Raum persistent machen?"}.
{"Make room moderated?", "Den Raum moderiert machen?"}.
{"Default users as members?",
"Standardbenutzer als Mitglieder ansehen?"}.
{"Make room members only?",
"Den Raum nur für Mitglieder zugänglich machen?"}.
{"Allow users to send invites?",
"Benutzer dürfen Einladungen senden?"}.
{"Make room password protected?", "Den Raum passwort geschützt machen?"}.
{"Password", "Passwort"}.
{"Make room anonymous?", "Den Raum anonym machen?"}.
{"Enable logging?", "Nachrichten loggen?"}.
{"Only moderators and participants are allowed to change subject in this room",
"Nur Moderatoren und Teilnehmer dürfen das Thema in diesem Raum ändern"}.
{"Only moderators are allowed to change subject in this room",
@@ -184,7 +160,7 @@
{"private, ", "privat"}.
% mod_irc/mod_irc.erl
{"ejabberd IRC module\nCopyright (c) 2003-2005 Alexey Shchepin", "ejabberd IRC module\nCopyright (c) 2003-2005 Alexey Shchepin"}.
{"ejabberd IRC module\nCopyright (c) 2003-2006 Alexey Shchepin", "ejabberd IRC module\nCopyright (c) 2003-2006 Alexey Shchepin"}.
{"You need an x:data capable client to configure mod_irc settings",
"Sie brauchen einen x:data kompatiblen Client um die mod_irc Einstellungen zu konfigurieren"}.
{"Registration in mod_irc for ", "Registrierung in mod_irc für "}.
@@ -197,85 +173,51 @@
{"If you want to specify different encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\"}'. By default this service use \"~s\" encoding.", "Wenn Sie verschiedene Verschlüsselungen für IRC Server angeben, schreiben Sie diese im Format '{\"irc server\", \"encoding\"}'. Standardmäßig benutzt der Dienst die \"~s\" Verschlüsselung"}.
% web/ejabberd_web_admin.erl
{"ejabberd administration", "Ejabberd Administration"}.
{"Users", "Benutzer"}.
{"Nodes", "Knoten(nodes)"}.
{"Statistics", "Statistik"}.
{"submitted", "bestätigt"}.
{"bad format", "ungültiges Format"}.
{"ejabberd access control lists configuration", "Ejabberd access control lists Konfiguration"}.
{"Delete Selected", "Lösche Markiertes"}.
{"Submit", "Bestätigen"}.
{"ejabberd access rules configuration", "Ejabberd Zutrittsregeln Konfiguration"}.
{"~s access rule configuration", "~s Zurtrittsregel Konfiguration"}.
{"ejabberd users", "Ejabberd Benutzer"}.
{"ejabberd stats", "Ejabberd Statistik"}.
{"Node not found", "Knoten nicht gefunden"}.
{"Add New", "Neuen hinzufügen"}.
{"Registered users", "Registrierte Benutzer"}.
{"Online users", "Online Benutzer"}.
{"Outgoing S2S servers", "Rausgehende S2S Server"}.
{"Change Password", "Passwort ändern"}.
{"Connected Resources:", "Verbundene Resourcen"}.
{"Password:", "Passwort:"}.
{"None", "Keine"}.
{"Node ", "Knoten "}.
{"DB Management", "DB Management"}.
{"Listened Ports Management", "Ports Management"}.
{"Restart", "Neustart"}.
{"Stop", "Stop"}.
{"RPC call error", "RPC-Aufruf Fehler"}.
{"DB Tables at ", "DB Tabellen bei "}.
{"Name", "Name"}.
{"Storage Type", "Speichertyp"}.
{"Size", "Größe"}.
{"Memory", "Speicher"}.
{"Backup Management at ", "Backup Management bei "}.
{"Store a backup in a file", "Die Sicherung in eine Datei legen "}.
{"OK", "OK"}.
{"Restore a backup from a file", "Die Sicherung aus einer Datei wiederherstellen"}.
{"Install a database fallback from a file", "Eine Sicherungsdatenbank aus einer Datei installieren"}.
{"Dump a database in a text file", "Ausgabe der Datenbank in eine Textdatei"}.
{"Restore a database from a text file", "Datenbank aus einer Textdatei wiederherstellen"}.
{"Listened Ports at ", "Listened Ports at "}.
{"~p statistics", "~p Statistiken"}.
{"Uptime", "Betriebszeit"}.
{"CPU Time", "CPU Zeit"}.
{"Transactions commited", "abgeschlossene Vorgänge"}.
{"Transactions aborted", "abgebrochene Vorgänge"}.
{"Transactions restarted", "neugestartete Vorgänge"}.
{"Transactions logged", "geloggte Vorgänge"}.
{"Listened Ports at ", "Überwachte Ports "}.
{"Port", "Port"}.
{"Module", "Modul"}.
{"Options", "Optionen"}.
{"Update", "Aktualisieren"}.
{"Delete", "Löschen"}.
{"Add User", "Benutzer hinzufügen"}.
{"Offline messages", "Offline Nachrichten"}.
{"Last Activity", "Letzte Aktion"}.
{"Never", "Nie"}.
{"~s offline messages queue", "~s offline Nachrichtenschlange"}.
{"Time", "Uhrzeit"}.
{"From", "Von"}.
{"To", "Zu"}.
{"Packet", "Paket"}.
{"Offline messages:", "Offline Nachrichten"}.
{"Roster", "Liste"}.
{"Nickname", "Spitzname"}.
{"Subscription", "Subskription"}.
{"Pending", "schwebend"}.
{"Groups", "Gruppe"}.
{"Remove", "Entfernen"}.
{"Add JID", "JID hinzufügen"}.
{"User ", "Benutzer "}.
{"Roster of ", "Liste von "}.
{"Virtual Hosts", "Virtuelle Hosts"}.
{"ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One", "ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One"}.
{"ejabberd (c) 2002-2006 Alexey Shchepin, 2004-2006 Process One", "ejabberd (c) 2002-2006 Alexey Shchepin, 2004-2006 Process One"}.
{"Shared Roster", "Gemeinsame Kontaktliste"}.
{"(raw)", "(roh)"}.
{"raw", "roh"}.
{"ejabberd virtual hosts", "ejabberd virtuelle Hosts"}.
{"Users last activity", "Letzte Aktivität des Benutzers"}.
{"Period: ", "Zeitraum"}.
{"Last month", "Letzter Monat"}.
{"Last year", "Letztes Jahr"}.
@@ -284,14 +226,9 @@
{"Show Integral Table", "Vollständige Tabelle anzeigen"}.
{"Host", "Host"}.
{"Online", "Online"}.
{"Authenticated users", "Angemeldete Benutzer"}.
{"Validate", "Validieren"}.
{"No data", "Keine Daten"}.
{"Modules Management", "Modulverwaltung"}.
{"Modules at ", "Module auf "}.
{"Not found", "Nicht gefunden"}.
{"Start", "Anfang"}.
{"Shared roster groups", "Gruppen der gemeinsamen Kontaktliste"}.
{"Name:", "Name"}.
{"Description:", "Beschreibung:"}.
{"Members:", "Mitglieder:"}.
@@ -299,17 +236,132 @@
{"Group ", "Gruppe "}.
{"ejabberd Web Interface", "ejabberd Web Interface"}.
% mod_vcard_ldap.erl
{"Given Name", "Vorname"}.
% ejabberd_c2s.erl
{"Use of STARTTLS required", "Verwendung von STARTTLS erforderlich"}.
{"Replaced by new connection", "Durch neue Verbindung ersetzt"}.
% mod_vcard_odbc.erl
{"Erlang Jabber Server\nCopyright (c) 2002-2005 Alexey Shchepin", "Erlang Jabber Server\nCopyright (c) 2002-2005 Alexey Shchepin"}.
{"Erlang Jabber Server\nCopyright (c) 2002-2006 Alexey Shchepin", "Erlang Jabber Server\nCopyright (c) 2002-2006 Alexey Shchepin"}.
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)", "Füllen Sie die Felder aus, um nach passenden Jabber benutzern zu suchen (beenden Sie ein Feld mit *, um auch nach Teilzeichenketten zu suchen)"}.
{"JID", "JID"}.
{"Email", "E-Mail"}.
{"Search Results for ", "Suchergebnisse für"}.
{"Jabber ID", "Jabber ID"}.
% mod_adhoc.erl
{"Commands", "Befehle"}.
{"Ping", "Ping"}.
{"Pong", "Pong"}.
% mod_announce.erl
{"Really delete message of the day?", "Wirklich die Nachricht des Tages löschen ?"}.
{"Subject", "Betreff"}.
{"Message body", "Nachrichtentext"}.
{"No body provided for announce message", "Keinen Text für die Ankündigung angegeben"}.
{"Announcements", "Ankündigungen"}.
{"Send announcement to all users", "Sende Ankündigung an alle Benutzer"}.
{"Send announcement to all online users", "Sende Ankündigung an alle online Benutzer"}.
{"Send announcement to all online users on all hosts", "Sende Ankündigung an alle online Benutzer auf allen Hosts"}.
{"Set message of the day and send to online users", "Setze Nachricht des Tages und sende sie an alle online Benutzer"}.
{"Update message of the day (don't send)", "Aktualisiere Nachricht des Tages (nicht senden)"}.
{"Delete message of the day", "Lösche Nachricht des Tages"}.
% mod_configure.erl
{"Database", "Datenbank"}.
{"Outgoing s2s Connections", "Ausgehende s2s Verbindungen"}.
{"Import Users From jabberd 1.4 Spool Files", "Importiere Benutzer von jabberd 1.4 Spool Dateien"}.
{"Database Tables Configuration at ", "Datenbank Tabellen Konfiguration bei "}.
% web/ejabberd_web_admin.erl
{"Administration", "Verwaltung"}.
{"(Raw)", "(Roh)"}.
{"Submitted", "Bestätigt"}.
{"Bad format", "Ungültiges Format"}.
{"Raw", "Roh"}.
{"Users Last Activity", "Letzte Benutzeraktivität"}.
{"Registered Users", "Registrierte Benutzer"}.
{"Offline Messages", "Offline Nachrichten"}.
{"Registered Users:", "Registrierte Benutzer:"}.
{"Authenticated Users:", "Überprüfte Benutzer:"}.
{"Online Users:", "Online Benutzer:"}.
{"Outgoing s2s Connections:", "Ausgehende s2s Verbindungen:"}.
{"Outgoing s2s Servers:", "Ausgehende s2s Server:"}.
{"Offline Messages:", "Offline Nachrichten:"}.
{"~s's Offline Messages Queue", "~s's Offline Nachrichten Warteschlange"}.
{"Add Jabber ID", "Jabber ID hinzufügen"}.
{"No Data", "Keine Daten"}.
{"Listened Ports", "Überwachte Ports"}.
{"RPC Call Error", "RPC Abruf-Fehler"}.
{"Database Tables at ", "Datenbank Tabellen bei "}.
{"Backup of ", "Sicherung von "}.
{"Remark that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.", "Beachten sie, daß diese Optionen nur die eingebaute Mnesia Datenbank sichert. Wenn sie das ODBC Modul verwenden, müssen sie die SQL Datenbank manuell sichern."}.
{"Store binary backup:", "Speichere binäres Sicherung:"}.
{"Restore binary backup immediately:", "Stelle binäre Sicherung sofort wiederher:"}.
{"Restore binary backup after next ejabberd restart (requires less memory):", "Stelle binäre Sicherung beim nächsten ejabberd Neustart wiederher (benötigt weniger Speicher):"}.
{"Store plain text backup:", "Speichere plaintext Sicherung:"}.
{"Restore plain text backup immediately:", "Stelle plaintext Sicherung sofort wiederher:"}.
{"Statistics of ~p", "Statistiken von ~p"}.
{"Uptime:", "Betriebszeit:"}.
{"CPU Time:", "CPU Zeit:"}.
{"Transactions Commited:", "Vorgänge durchgeführt:"}.
{"Transactions Aborted:", "Vorgänge abgebrochen:"}.
{"Transactions Restarted:", "Vorgänge neu gestartet:"}.
{"Transactions Logged:", "Vorgänge protokolliert:"}.
{"Update ", "Aktualisierung"}.
{"Update plan", "Aktualisierungsplan"}.
{"Updated modules", "Aktualisierte Module"}.
{"Update script", "Aktualisierungsscript"}.
{"Low level update script", "Low level Aktualisierungsscript"}.
{"Script check", "Script Überprüfung"}.
{"Not Found", "Nicht gefunden"}.
{"Shared Roster Groups", "Gruppen der gemeinsamen Kontaktliste"}.
% mod_muc/mod_muc_log.erl
{"Chatroom configuration modified", "Chatraum Konfiguration verändert"}.
{"joins the room", "kommt in den Raum"}.
{"leaves the room", "verlässt den Raum"}.
{"has been kicked", "wurde gekickt"}.
{"has been banned", "wurde gebannt"}.
{"is now known as", "ist nun bekannt als"}.
{"Monday", "Montag"}.
{"Tuesday", "Dienstag"}.
{"Wednesday", "Mittwoch"}.
{"Thursday", "Donnerstag"}.
{"Friday", "Freitag"}.
{"Saturday", "Samstag"}.
{"Sunday", "Sonntag"}.
{"January", "Jänner"}.
{"February", "Feber"}.
{"March", "März"}.
{"April", "April"}.
{"May", "Mai"}.
{"June", "Juni"}.
{"July", "Juli"}.
{"August", "August"}.
{"September", "September"}.
{"October", "Oktober"}.
{"November", "November"}.
{"December", "Dezember"}.
{"Room Configuration", "Raum Konfiguration"}.
% mod_muc/mod_muc_room.erl
{"This room is not anonymous", "Dieser Raum ist nicht anonym"}.
{"Make room persistent", "Raum persistent machen"}.
{"Make room public searchable", "Raum öffentlich durchsuchbar machen"}.
{"Make participants list public", "Teilnehmerliste öffentlich machen"}.
{"Make room password protected", "Raum passwortgeschützt machen"}.
{"Make room members-only", "Raum members-only machen"}.
{"Make room moderated", "Raum modieriert machen"}.
{"Default users as participants", "Standartbenutzer als Teilnehmer"}.
{"Allow users to change subject", "Erlaube Benutzern Thema zu ändern"}.
{"Allow users to send private messages", "Erlaube Benutzern private Nachrichten zu senden"}.
{"Allow users to query other users", "Erlaube Benutzern andere Benutzer zu sehen(query)"}.
{"Allow users to send invites", "Erlaube Benutzern Einladungen zu senden"}.
{"Enable logging", "Log-Funktion aktivieren"}.
{"Description", "Beschreibung"}.
{"Number of occupants", "Anzahl der Teilnehmer"}.
{"Present real JIDs to", "Echte Jabber IDs anzeigen für"}.
{"moderators only", "nur Moderatoren"}.
{"anyone", "jeden"}.
% Local Variables:
% mode: erlang

Some files were not shown because too many files have changed in this diff Show More