Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1eef9f84b | |||
| 441d31ac3e | |||
| fcc4adcde5 | |||
| 9057197446 | |||
| 8515833954 | |||
| 51fa955e11 | |||
| 7e948fbbac | |||
| 9bf5a83598 | |||
| de306d0364 | |||
| 616a273286 | |||
| 7214549508 | |||
| c0f898bbb8 | |||
| 4cb24c3e6a | |||
| 910d6667eb | |||
| 16c2d399e9 | |||
| fef7c626e9 | |||
| 54d05f64ba | |||
| 30371b2483 | |||
| 2a542e9e09 | |||
| 11da06b5e7 | |||
| d9487135c0 | |||
| 1451034393 | |||
| 539000bf62 | |||
| d7e96b89ec | |||
| 4646bf818a | |||
| f5750063f0 | |||
| 751fbec0b6 | |||
| 2923c6a962 | |||
| 37f56859e7 | |||
| 041004c042 | |||
| 9d7006dfa2 | |||
| 1c2195ebdb | |||
| b2acdac5fa | |||
| c11746181d | |||
| 3ab30f34fb | |||
| 5d4093b8ac | |||
| 3a3682a7b3 | |||
| 7dbb35800d | |||
| 6a3afa778b | |||
| aa478100d8 | |||
| d574757de4 | |||
| 9ababf2a1d | |||
| 5b9526d998 | |||
| 519e78eed6 | |||
| 79f5d2772e | |||
| b2e83ffca6 | |||
| 78aeb15aac | |||
| 322fc0a6b8 | |||
| d786782a0f | |||
| ccfcfa49fa | |||
| ce3bdf52d9 | |||
| e734ad1682 | |||
| bb4f9f63b9 | |||
| 4ea5e493ea | |||
| 112c609222 | |||
| d31ab5ca13 | |||
| 33431d1a45 | |||
| 1632aec61e | |||
| b7fc7fdada | |||
| 8583e3f9be | |||
| 23b78b0f0c | |||
| 677a6004cf | |||
| ec668cd85a | |||
| 0b660a42f4 | |||
| 8c8c7b1e3f | |||
| 4211540272 | |||
| 6bdabb0c3b | |||
| 647acc7768 | |||
| da53c02892 | |||
| 7d2a1af9d9 | |||
| 6acc96c171 | |||
| a1d696c36b | |||
| 3bb802a650 | |||
| a5e4d5d550 | |||
| d97eaa26d5 | |||
| 0fe9caa304 | |||
| 4d7cc9301a | |||
| d0e2ac9ed2 | |||
| 40c8c8b559 | |||
| a7f22bfded | |||
| d3b1bbda47 | |||
| 66561c3602 | |||
| cc4d404364 | |||
| 81f9ffcd45 | |||
| ab2e19a399 | |||
| 9b8dcac5a7 | |||
| e965aae60a | |||
| 6290bac932 | |||
| 6225c951fd | |||
| a166203194 | |||
| 6bf03a683d | |||
| 9fdef5c15f | |||
| 110c5290b2 | |||
| 230d66b168 | |||
| 2b46e3eaa7 | |||
| 7d0f9ddce2 | |||
| bd276f0b07 | |||
| b80002a8bb | |||
| 9dcd3f4080 | |||
| 05c50cc5ca | |||
| ae537d2bfb | |||
| ce5d10eb59 | |||
| 7ec4c9c119 | |||
| 02dd41c178 | |||
| c4e98624c3 | |||
| c737ce1b8c | |||
| 42cdf80cd4 | |||
| 753e768090 | |||
| 043c2ccf51 | |||
| 21b912a375 | |||
| 177b367cbe | |||
| 640e1579f5 | |||
| 205c565e6f | |||
| ba55665381 | |||
| c57ca8a483 | |||
| 656c1c1570 | |||
| 719306c999 | |||
| b86e1a9e5b | |||
| 960e66423d | |||
| 66149f862f | |||
| bd4c18516c | |||
| d3777ba590 | |||
| 3e3d78effc | |||
| 7483d337b4 | |||
| ccd28e0e5b | |||
| f01dea67a0 | |||
| 35c42e2592 | |||
| 29cec06fea | |||
| 957ddb325e | |||
| 1694a6a756 | |||
| 25ca5013bb | |||
| c86275ce9d | |||
| dc198cf08e | |||
| d5309f0af4 | |||
| 8eb823abbb | |||
| c4b976881e | |||
| e0c700e725 | |||
| 8688bc508f | |||
| 3cce56b9f8 | |||
| 4797060eb9 | |||
| 15a714ae68 | |||
| aa8e532cbd | |||
| 681f0b9567 | |||
| 49917e284e | |||
| 0e1f86e1f5 | |||
| 9a44fdffab | |||
| a5a978946d | |||
| ed43f8024c | |||
| 3e9e51ee30 | |||
| f3085d09e4 | |||
| 832279f1f2 | |||
| 89e57d421e | |||
| a6b3e6c904 | |||
| eabf8d1c6c | |||
| 7278a606a8 | |||
| d5137b0563 | |||
| 4f2a355408 | |||
| d837cb9e8d | |||
| 804c32c100 | |||
| 17420e058b | |||
| 20d049fe28 | |||
| 51c4ab8351 | |||
| 80181dc618 | |||
| 0fd01cfff8 | |||
| 44fdd72041 | |||
| 0234b83be4 | |||
| 904300ce04 | |||
| 3eb1b59e64 | |||
| 73073d4af5 | |||
| 6eac737807 | |||
| b8e99a3c05 | |||
| 2f0cf8bce8 | |||
| 0cc168a9aa | |||
| cf10843071 | |||
| dc57e75e8f | |||
| facefdb6e4 | |||
| 971001e7aa | |||
| 50f44530d2 | |||
| ef456ab645 | |||
| 90618de7be | |||
| efacb0ad66 | |||
| 976a616662 | |||
| ef0b3ab24c | |||
| 2d7fbf3964 | |||
| a4caafeb72 | |||
| 1295893864 | |||
| 568909d5bb | |||
| c7bafe0056 | |||
| 54a1ced6f6 | |||
| 6bb510d99e | |||
| 8401a5ac55 | |||
| 7ee3e45df1 | |||
| 784edef4b6 | |||
| 254a8c1b60 | |||
| 3cbed307bc | |||
| 08af86cb11 | |||
| 15e747782a | |||
| 0035c63399 | |||
| 559b5cae14 | |||
| c275a24597 | |||
| 52fcdc716d | |||
| 9064851ab5 | |||
| 5f2de53f01 | |||
| 689842edf8 | |||
| aae0124f61 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 –
|
||||
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 Introduction</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc2">1.1 How it works</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc3">1.1.1 Router</A>
|
||||
<LI><A HREF="#htoc4">1.1.2 Local Router</A>
|
||||
<LI><A HREF="#htoc5">1.1.3 Session Manager</A>
|
||||
<LI><A HREF="#htoc6">1.1.4 S2S Manager</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc2">1.1 Key Features</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc3">1.2 Additional Features</A>
|
||||
</UL>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc4">2 How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc5">2.1 Router</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc6">2.2 Local Router</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc7">2.3 Session Manager</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc8">2.4 S2S Manager</A>
|
||||
</UL>
|
||||
<LI><A HREF="#htoc7">2 XML representation</A>
|
||||
<LI><A HREF="#htoc8">3 Module <TT>xml</TT></A>
|
||||
<LI><A HREF="#htoc9">4 Module <TT>xml_stream</TT></A>
|
||||
<LI><A HREF="#htoc10">5 <TT>ejabberd</TT> modules</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc11">5.1 <CODE>gen_mod</CODE> behaviour</A>
|
||||
<LI><A HREF="#htoc12">5.2 Module <CODE>gen_iq_handler</CODE></A>
|
||||
<LI><A HREF="#htoc13">5.3 Services</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc9">3 XML Representation</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc10">4 Module <TT>xml</TT></A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc11">5 Module <TT>xml_stream</TT></A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc12">6 Modules</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc13">6.1 Module gen_iq_handler</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc14">6.2 Services</A>
|
||||
</UL>
|
||||
</UL>
|
||||
|
||||
<!--TOC section Introduction-->
|
||||
|
||||
<H2><A NAME="htoc1">1</A> Introduction</H2><!--SEC END -->
|
||||
<H2 CLASS="section"><A NAME="htoc1">1</A> <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> How it works</H3><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc2">1.1</A> <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> <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> 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> Router</H4><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc5">2.1</A> 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> Local Router</H4><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc6">2.2</A> 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> Session Manager</H4><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc7">2.3</A> 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> S2S Manager</H4><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc8">2.4</A> 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> XML representation</H2><!--SEC END -->
|
||||
<H2 CLASS="section"><A NAME="htoc9">3</A> 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. g. this stanza:
|
||||
<PRE>
|
||||
<PRE CLASS="verbatim">
|
||||
<message to='test@conference.example.org' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
</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> Module <TT>xml</TT></H2><!--SEC END -->
|
||||
<H2 CLASS="section"><A NAME="htoc10">4</A> Module <TT>xml</TT></H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:xmlmod"></A>
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>element_to_string(El) -> string()</B></CODE><DD>
|
||||
<PRE>
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>element_to_string(El) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
El = XMLElement
|
||||
</PRE>Returns string representation of XML stanza <TT>El</TT>.<BR>
|
||||
<BR>
|
||||
<DT><CODE><B>crypt(S) -> string()</B></CODE><DD>
|
||||
<PRE>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>crypt(S) -> 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) -> EList</B></CODE><DD>
|
||||
<PRE>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -> 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) -> Res</B></CODE><DD>
|
||||
<PRE>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -> 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> Module <TT>xml_stream</TT></H2><!--SEC END -->
|
||||
<H2 CLASS="section"><A NAME="htoc11">5</A> Module <TT>xml_stream</TT></H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:xmlstreammod"></A>
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>parse_element(Str) -> XMLElement | {error, Err}</B></CODE><DD>
|
||||
<PRE>
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>parse_element(Str) -> 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> <TT>ejabberd</TT> modules</H2><!--SEC END -->
|
||||
<H2 CLASS="section"><A NAME="htoc12">6</A> 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> <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> Module <CODE>gen_iq_handler</CODE></H3><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc13">6.1</A> 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}) ->
|
||||
</PRE>
|
||||
<!--TOC subsection Services-->
|
||||
|
||||
<H3><A NAME="htoc13">5.3</A> Services</H3><!--SEC END -->
|
||||
<H3 CLASS="subsection"><A NAME="htoc14">6.2</A> 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) ->
|
||||
<!--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
@@ -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).
|
||||
|
||||
|
||||
@@ -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 –
|
||||
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. —
|
||||
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. —
|
||||
Peter Saint-André, 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 —
|
||||
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>
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+1831
-727
File diff suppressed because it is too large
Load Diff
+54
-54
@@ -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}
|
||||
+66
-65
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -1,2 +1,2 @@
|
||||
% Define ejabberd version here.
|
||||
\newcommand{\version}{1.0.0}
|
||||
\newcommand{\version}{1.1.2}
|
||||
|
||||
+18
-6
@@ -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
@@ -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
@@ -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}) ++
|
||||
|
||||
Vendored
+32
-1
@@ -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
@@ -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}.
|
||||
@@ -0,0 +1,15 @@
|
||||
-record(adhoc_request, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
action,
|
||||
xdata,
|
||||
others}).
|
||||
|
||||
-record(adhoc_response, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
status,
|
||||
defaultaction = "",
|
||||
actions = [],
|
||||
notes = [],
|
||||
elements = []}).
|
||||
Vendored
+2714
-1599
File diff suppressed because it is too large
Load Diff
+34
-2
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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}}.
|
||||
|
||||
@@ -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
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "1.0.0"},
|
||||
{vsn, "1.1.2"},
|
||||
{modules, [acl,
|
||||
configure,
|
||||
cyrsasl,
|
||||
|
||||
@@ -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
@@ -6,7 +6,7 @@
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(VERSION, "1.0.0").
|
||||
-define(VERSION, "1.1.2").
|
||||
|
||||
%-define(ejabberd_debug, true).
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
@@ -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) -> [].
|
||||
@@ -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
@@ -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].
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
@@ -12,7 +12,8 @@ OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
OBJS = \
|
||||
$(OUTDIR)/eldap.beam \
|
||||
$(OUTDIR)/ELDAPv3.beam
|
||||
$(OUTDIR)/ELDAPv3.beam \
|
||||
$(OUTDIR)/eldap_filter.beam
|
||||
|
||||
all: $(OBJS)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+15
-26
@@ -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
@@ -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
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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\"><~s></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\"><</a> <a class=\"nav\" href=\".\/\">^</a> <a class=\"nav\" href=\"~s\">></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, "\\&", "\\&")),
|
||||
S3 = element(2, regexp:gsub(S2, "<", "\\<")),
|
||||
S4 = element(2, regexp:gsub(S3, ">", "\\>")),
|
||||
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
@@ -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.
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -13,6 +13,6 @@
|
||||
subscription = none,
|
||||
ask = none,
|
||||
groups = [],
|
||||
xattrs = [],
|
||||
askmessage = [],
|
||||
xs = []}).
|
||||
|
||||
|
||||
+150
-208
@@ -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).
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+35
-50
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user