diff --git a/CHANGELOG.md b/CHANGELOG.md index 779345ee..8f38e98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ * make it send pre-recorded messages every X minutes, * respond message to custom commands. * many other features will be available in future releases! +* New settings: "Ban anonymous user's IP when user is banned from a chatroom": + * if enabled, every time a streamer bans an anonymous user, it will ban its IP on the chat server, + * banned IPs are logged on disk, so server's admin can use them to feed fail2ban (for example), + * option disabled by default, because could be used to create trapped-rooms on public servers ### Minor changes and fixes diff --git a/client/admin-plugin-client-plugin.ts b/client/admin-plugin-client-plugin.ts index d52e6f9f..db3d1349 100644 --- a/client/admin-plugin-client-plugin.ts +++ b/client/admin-plugin-client-plugin.ts @@ -240,6 +240,8 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re return options.formValues['converse-theme'] !== 'peertube' case 'chat-per-live-video-warning': return !(options.formValues['chat-all-lives'] === true && options.formValues['chat-per-live-video'] === true) + case 'auto-ban-anonymous-ip': + return options.formValues['chat-no-anonymous'] !== false } return false diff --git a/languages/en.yml b/languages/en.yml index bda2c764..def43704 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -122,6 +122,13 @@ no_anonymous_description: | If you enabled it, it is highly recommended to also check "Don't publish chat information". Otherwise, some third party tools could try to open the chat, and have unpredictable behaviours. +auto_ban_anonymous_ip_label: "Ban anonymous user's IP when user is banned from a chatroom" +auto_ban_anonymous_ip_description: | + By enabling this option, each time an anonymous user is banned from a chatroom, it's IP will also be banned from the chat server. + Warning: if your instance is open to registration, any user could create a trapped-room, invite users to join, and automatically ban all anonymous user's IPs. + The banned IP list is not stored, it will be cleared on server restart, or when you change some plugin's settings. + The banned IP are logged in the Prosody server log files, so server's administrators can eventually use some external tools (like fail2ban) to ban IPs more widely. + theming_advanced_description: "

Theming

" converse_theme_label: "ConverseJS theme" diff --git a/prosody-modules/mod_muc_ban_ip/README.markdown b/prosody-modules/mod_muc_ban_ip/README.markdown new file mode 100644 index 00000000..d43956c8 --- /dev/null +++ b/prosody-modules/mod_muc_ban_ip/README.markdown @@ -0,0 +1,63 @@ +--- +labels: +- 'Stage-Alpha' +summary: Ban users from chatrooms by their IP address +... + +Note: this is a slightly modified version: the log level for IP bans is +set to info, instead of debug. +So we can use external tools (fail2ban for example) to block IPs more widely. + +Introduction +============ + +One frequent complaint about XMPP chatrooms (MUCs) compared to IRC is +the inability for a room admin to ban a user based on their IP address. +This is because an XMPP user is not identified on the network by their +IP address, only their JID. + +This means that it is possible to create a new account (usually quite +easily), and rejoin the room that you were banned from. + +This module allows the **user's** server to enforce bans by IP address, +which is very desirable for server admins who want to prevent their +server being used for spamming and abusive behaviour. + +Details +======= + +An important point to note is that this module enforces the IP ban on +the banned user's server, not on the MUC server. This means that: + +- The user's server MUST have this module loaded, however - +- The module works even when the MUC is on a different server to the + user +- The MUC server does not need this module (it only needs to support + the [standard ban + protocol](http://xmpp.org/extensions/xep-0045.html#ban)) +- The module works for effectively banning [anonymous + users](http://prosody.im/doc/anonymous_logins) + +Also note that IP bans are not saved permanently, and are reset upon a +server restart. + +Configuration +============= + +There is no extra configuration for this module except for loading it. +Remember... do not load it on the MUC host, simply add it to your global +`modules_enabled` list, or under a specific host like: + +``` lua +VirtualHost "anon.example.com" + authentication = "anonymous" + modules_enabled = { "muc_ban_ip" } +``` + +Compatibility +============= + + ----- -------------- + 0.9 Works + 0.8 Doesn't work + ----- -------------- diff --git a/prosody-modules/mod_muc_ban_ip/mod_muc_ban_ip.lua b/prosody-modules/mod_muc_ban_ip/mod_muc_ban_ip.lua new file mode 100644 index 00000000..9dba11e1 --- /dev/null +++ b/prosody-modules/mod_muc_ban_ip/mod_muc_ban_ip.lua @@ -0,0 +1,80 @@ +module:set_global(); + +local jid_bare, jid_host = require "util.jid".bare, require "util.jid".host; +local st = require "util.stanza"; +local xmlns_muc_user = "http://jabber.org/protocol/muc#user"; + +local trusted_services = module:get_option_inherited_set("muc_ban_ip_trusted_services", {}); +local trust_local_restricted_services = module:get_option_boolean("muc_ban_ip_trust_local_restricted_services", true); + +local ip_bans = module:shared("bans"); +local full_sessions = prosody.full_sessions; + +local function is_local_restricted_service(host) + local muc_service = prosody.hosts[host] and prosody.hosts[host].modules.muc; + if muc_service and module:context(host):get_option("restrict_room_creation") ~= nil then -- COMPAT: May need updating post-0.12 + return true; + end + return false; +end + +local function ban_ip(session, from) + local ip = session.ip; + if not ip then + module:log("warn", "Failed to ban IP (IP unknown) for %s", session.full_jid); + return; + end + local from_host = jid_host(from); + if trusted_services:contains(from_host) or (trust_local_restricted_services and is_local_restricted_service(from_host)) then + from = from_host; -- Ban from entire host + end + local banned_from = ip_bans[ip]; + if not banned_from then + banned_from = {}; + ip_bans[ip] = banned_from; + end + banned_from[from] = true; + -- Specific to peertube-plugin-livechat: log level=info. + module:log("info", "Added ban for IP address %s from %s", ip, from); +end + +local function check_for_incoming_ban(event) + local stanza = event.stanza; + local to_session = full_sessions[stanza.attr.to]; + if to_session then + local directed = to_session.directed; + local from = stanza.attr.from; + if directed and directed[from] and stanza.attr.type == "unavailable" then + -- This is a stanza from somewhere we sent directed presence to (may be a MUC) + local x = stanza:get_child("x", xmlns_muc_user); + if x then + for status in x:childtags("status") do + if status.attr.code == '301' then + ban_ip(to_session, jid_bare(from)); + end + end + end + end + end +end + +local function check_for_ban(event) + local origin, stanza = event.origin, event.stanza; + local ip = origin.ip; + local to, to_host = jid_bare(stanza.attr.to), jid_host(stanza.attr.to); + if ip_bans[ip] and (ip_bans[ip][to] or ip_bans[ip][to_host]) then + (origin.log or module._log)("debug", "IP banned: %s is banned from %s", ip, to) + if stanza.attr.type ~= "error" then + origin.send(st.error_reply(stanza, "auth", "forbidden") + :tag("x", { xmlns = xmlns_muc_user }) + :tag("status", { code = '301' })); + end + return true; + end + (origin.log or module._log)("debug", "IP not banned: %s from %s", ip, to) +end + +function module.add_host(module) + module:hook("presence/full", check_for_incoming_ban, 100); + module:hook("pre-presence/full", check_for_ban, 100); +end diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index cec2ee34..19cb2c42 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -152,6 +152,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise=5.0.0, and is a prerequisite to websocket diff --git a/server/lib/prosody/config/content.ts b/server/lib/prosody/config/content.ts index c99598d0..5bddf818 100644 --- a/server/lib/prosody/config/content.ts +++ b/server/lib/prosody/config/content.ts @@ -207,10 +207,13 @@ class ProsodyConfigContent { this.muc.set('muc_room_default_history_length', 20) } - useAnonymous (): void { + useAnonymous (autoBanIP: boolean): void { this.anon = new ProsodyConfigVirtualHost('anon.' + this.prosodyDomain) this.anon.set('authentication', 'anonymous') this.anon.set('modules_enabled', ['ping']) + if (autoBanIP) { + this.anon.add('modules_enabled', 'muc_ban_ip') + } } useHttpAuthentication (url: string): void { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 15a2a8b2..09c6d95e 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -83,7 +83,7 @@ Please read private: true }) - // ********** Moderation and advances customization + // ********** Advanced channel customization registerSetting({ type: 'html', private: true, @@ -197,6 +197,14 @@ Please read descriptionHTML: loc('no_anonymous_description'), private: false }) + registerSetting({ + name: 'auto-ban-anonymous-ip', + label: loc('auto_ban_anonymous_ip_label'), + type: 'input-checkbox', + default: false, + descriptionHTML: loc('auto_ban_anonymous_ip_description'), + private: true + }) // ********** Theming registerSetting({ diff --git a/support/documentation/content/en/documentation/admin/settings.md b/support/documentation/content/en/documentation/admin/settings.md index 2672d3ed..b4bed9e2 100644 --- a/support/documentation/content/en/documentation/admin/settings.md +++ b/support/documentation/content/en/documentation/admin/settings.md @@ -82,6 +82,10 @@ Note: for now this feature simply hide the chat. In a future release, the chat will be replaced by a message saying «please log in to [...]». See [v5.7.0 Release Notes](https://github.com/JohnXLivingston/peertube-plugin-livechat/blob/main/CHANGELOG.md#570) for more information. +### {{% livechat_label auto_ban_anonymous_ip_label %}} + +{{% livechat_label auto_ban_anonymous_ip_description %}} + ## Theming ### {{% livechat_label converse_theme_label %}}