Merge pull request #1831 from YunoHost/handle-metronome-as-an-app

Handle metronome as an app
This commit is contained in:
Alexandre Aubin 2024-06-09 10:55:41 +02:00 committed by GitHub
commit 1e527a8214
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 42 additions and 1382 deletions

View file

@ -1,13 +1,9 @@
{% set interfaces_list = interfaces.split(' ') %}
{% for interface in interfaces_list %}
interface-name={{ domain }},{{ interface }}
interface-name=xmpp-upload.{{ domain }},{{ interface }}
{% endfor %}
{% if ipv6 %}
host-record={{ domain }},{{ ipv6 }}
host-record=xmpp-upload.{{ domain }},{{ ipv6 }}
{% endif %}
txt-record={{ domain }},"v=spf1 mx a -all"
mx-host={{ domain }},{{ domain }},5
srv-host=_xmpp-client._tcp.{{ domain }},{{ domain }},5222,0,5
srv-host=_xmpp-server._tcp.{{ domain }},{{ domain }},5269,0,5

View file

@ -1,75 +0,0 @@
VirtualHost "{{ domain }}"
enable = true
ssl = {
key = "/etc/yunohost/certs/{{ domain }}/key.pem";
certificate = "/etc/yunohost/certs/{{ domain }}/crt.pem";
}
authentication = "ldap2"
ldap = {
hostname = "localhost",
user = {
basedn = "ou=users,dc=yunohost,dc=org",
filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))",
usernamefield = "mail",
namefield = "cn",
},
}
-- Discovery items
disco_items = {
{ "muc.{{ domain }}" },
{ "pubsub.{{ domain }}" },
{ "jabber.{{ domain }}" },
{ "vjud.{{ domain }}" },
{ "xmpp-upload.{{ domain }}" },
};
-- contact_info = {
-- abuse = { "mailto:abuse@{{ domain }}", "xmpp:admin@{{ domain }}" };
-- admin = { "mailto:root@{{ domain }}", "xmpp:admin@{{ domain }}" };
-- };
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.
---Set up a MUC (multi-user chat) room server
Component "muc.{{ domain }}" "muc"
name = "{{ domain }} Chatrooms"
modules_enabled = {
"muc_limits";
"muc_log";
"muc_log_mam";
"muc_log_http";
"muc_vcard";
}
muc_event_rate = 0.5
muc_burst_factor = 10
room_default_config = {
logging = true,
persistent = true
};
---Set up a PubSub server
Component "pubsub.{{ domain }}" "pubsub"
name = "{{ domain }} Publish/Subscribe"
unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server)
---Set up a HTTP Upload service
Component "xmpp-upload.{{ domain }}" "http_upload"
name = "{{ domain }} Sharing Service"
http_file_path = "/var/xmpp-upload/{{ domain }}/upload"
http_external_url = "https://xmpp-upload.{{ domain }}:443"
http_file_base_path = "/upload"
http_file_size_limit = 6*1024*1024
http_file_quota = 60*1024*1024
http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes
http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes
---Set up a VJUD service
Component "vjud.{{ domain }}" "vjud"
vjud_disco_name = "{{ domain }} User Directory"

View file

@ -1,123 +0,0 @@
-- ** Metronome's config file example **
--
-- The format is exactly equal to Prosody's:
--
-- Lists are written { "like", "this", "one" }
-- Lists can also be of { 1, 2, 3 } numbers, etc.
-- Either commas, or semi-colons; may be used as seperators.
--
-- A table is a list of values, except each value has a name. An
-- example would be:
--
-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
--
-- Tip: You can check that the syntax of this file is correct when you have finished
-- by running: luac -p metronome.cfg.lua
-- If there are any errors, it will let you know what and where they are, otherwise it
-- will keep quiet.
-- Global settings go in this section
-- This is the list of modules Metronome will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended.
"saslauth"; -- Authentication for clients. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"disco"; -- Service discovery
-- Not essential, but recommended
"private"; -- Private XML storage (for room bookmarks, etc.)
"vcard"; -- Allow users to set vCards
"pep"; -- Allows setting of mood, tune, etc.
"pubsub"; -- Publish-subscribe XEP-0060
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
"bidi"; -- Enables Bidirectional Server-to-Server Streams.
-- Nice to have
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"stream_management"; -- Allows clients and servers to use Stream Management
"stanza_optimizations"; -- Allows clients to use Client State Indication and SIFT
"message_carbons"; -- Allows clients to enable carbon copies of messages
"mam"; -- Enable server-side message archives using Message Archive Management
"push"; -- Enable Push Notifications via PubSub using XEP-0357
"lastactivity"; -- Enables clients to know the last presence status of an user
"adhoc_cm"; -- Allow to set client certificates to login through SASL External via adhoc
"admin_adhoc"; -- administration adhoc commands
"bookmarks"; -- XEP-0048 Bookmarks synchronization between PEP and Private Storage
"sec_labels"; -- Allows to use a simplified version XEP-0258 Security Labels and related ACDFs.
"privacy"; -- Add privacy lists and simple blocking command support
-- Other specific functionality
--"admin_telnet"; -- administration console, telnet to port 5582
--"admin_web"; -- administration web interface
"bosh"; -- Enable support for BOSH clients, aka "XMPP over Bidirectional Streams over Synchronous HTTP"
--"compression"; -- Allow clients to enable Stream Compression
--"spim_block"; -- Require authorization via OOB form for messages from non-contacts and block unsollicited messages
--"gate_guard"; -- Enable config-based blacklisting and hit-based auto-banning features
--"incidents_handling"; -- Enable Incidents Handling support (can be administered via adhoc commands)
--"server_presence"; -- Enables Server Buddies extension support
--"service_directory"; -- Enables Service Directories extension support
--"public_service"; -- Enables Server vCard support for public services in directories and advertises in features
--"register_api"; -- Provides secure API for both Out-Of-Band and In-Band registration for E-Mail verification
"websocket"; -- Enable support for WebSocket clients, aka "XMPP over WebSockets"
};
-- Server PID
pidfile = "/var/run/metronome/metronome.pid"
-- HTTP server
http_ports = { 5290 }
http_interfaces = { "127.0.0.1", "::1" }
--https_ports = { 5291 }
--https_interfaces = { "127.0.0.1", "::1" }
-- Enable IPv6
use_ipv6 = true
-- BOSH configuration (mod_bosh)
consider_bosh_secure = true
cross_domain_bosh = true
-- WebSocket configuration (mod_websocket)
consider_websocket_secure = true
cross_domain_websocket = true
-- Disable account creation by default, for security
allow_registration = false
-- Use LDAP storage backend for all stores
storage = "ldap"
-- stanza optimization
csi_config_queue_all_muc_messages_but_mentions = false;
-- Logging configuration
log = {
info = "/var/log/metronome/metronome.log"; -- Change 'info' to 'debug' for verbose logging
error = "/var/log/metronome/metronome.err";
-- "*syslog"; -- Uncomment this for logging to syslog
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
}
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.
---Set up a local BOSH service
Component "localhost" "http"
modules_enabled = { "bosh" }
----------- Virtual hosts -----------
-- You need to add a VirtualHost entry for each domain you wish Metronome to serve.
-- Settings under each VirtualHost entry apply *only* to that host.
Include "conf.d/*.cfg.lua"

View file

@ -1,270 +0,0 @@
-- vim:sts=4 sw=4
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local ldap;
local connection;
local params = module:get_option("ldap");
local format = string.format;
local tconcat = table.concat;
local _M = {};
local config_params = {
hostname = 'string',
user = {
basedn = 'string',
namefield = 'string',
filter = 'string',
usernamefield = 'string',
},
groups = {
basedn = 'string',
namefield = 'string',
memberfield = 'string',
_member = {
name = 'string',
admin = 'boolean?',
},
},
admin = {
_optional = true,
basedn = 'string',
namefield = 'string',
filter = 'string',
}
}
local function run_validation(params, config, prefix)
prefix = prefix or '';
-- verify that every required member of config is present in params
for k, v in pairs(config) do
if type(k) == 'string' and k:sub(1, 1) ~= '_' then
local is_optional;
if type(v) == 'table' then
is_optional = v._optional;
else
is_optional = v:sub(-1) == '?';
end
if not is_optional and params[k] == nil then
return nil, prefix .. k .. ' is required';
end
end
end
for k, v in pairs(params) do
local expected_type = config[k];
local ok, err = true;
if type(k) == 'string' then
-- verify that this key is present in config
if k:sub(1, 1) == '_' or expected_type == nil then
return nil, 'invalid parameter ' .. prefix .. k;
end
-- type validation
if type(expected_type) == 'string' then
if expected_type:sub(-1) == '?' then
expected_type = expected_type:sub(1, -2);
end
if type(v) ~= expected_type then
return nil, 'invalid type for parameter ' .. prefix .. k;
end
else -- it's a table (or had better be)
if type(v) ~= 'table' then
return nil, 'invalid type for parameter ' .. prefix .. k;
end
-- recurse into child
ok, err = run_validation(v, expected_type, prefix .. k .. '.');
end
else -- it's an integer (or had better be)
if not config._member then
return nil, 'invalid parameter ' .. prefix .. tostring(k);
end
ok, err = run_validation(v, config._member, prefix .. tostring(k) .. '.');
end
if not ok then
return ok, err;
end
end
return true;
end
local function validate_config()
if true then
return true; -- XXX for now
end
-- this is almost too clever (I mean that in a bad
-- maintainability sort of way)
--
-- basically this allows a free pass for a key in group members
-- equal to params.groups.namefield
setmetatable(config_params.groups._member, {
__index = function(_, k)
if k == params.groups.namefield then
return 'string';
end
end
});
local ok, err = run_validation(params, config_params);
setmetatable(config_params.groups._member, nil);
if ok then
-- a little extra validation that doesn't fit into
-- my recursive checker
local group_namefield = params.groups.namefield;
for i, group in ipairs(params.groups) do
if not group[group_namefield] then
return nil, format('groups.%d.%s is required', i, group_namefield);
end
end
-- fill in params.admin if you can
if not params.admin and params.groups then
local admingroup;
for _, groupconfig in ipairs(params.groups) do
if groupconfig.admin then
admingroup = groupconfig;
break;
end
end
if admingroup then
params.admin = {
basedn = params.groups.basedn,
namefield = params.groups.memberfield,
filter = group_namefield .. '=' .. admingroup[group_namefield],
};
end
end
end
return ok, err;
end
-- what to do if connection isn't available?
local function connect()
return ldap.open_simple(params.hostname, params.bind_dn, params.bind_password, params.use_tls);
end
-- this is abstracted so we can maintain persistent connections at a later time
function _M.getconnection()
return connect();
end
function _M.getparams()
return params;
end
-- XXX consider renaming this...it doesn't bind the current connection
function _M.bind(username, password)
local conn = _M.getconnection();
local filter = format('%s=%s', params.user.usernamefield, username);
if params.user.usernamefield == 'mail' then
filter = format('mail=%s@*', username);
end
if filter then
filter = _M.filter.combine_and(filter, params.user.filter);
end
local who = _M.singlematch {
attrs = params.user.usernamefield,
base = params.user.basedn,
filter = filter,
};
if who then
who = who.dn;
module:log('debug', '_M.bind - who: %s', who);
else
module:log('debug', '_M.bind - no DN found for username = %s', username);
return nil, format('no DN found for username = %s', username);
end
local conn, err = ldap.open_simple(params.hostname, who, password, params.use_tls);
if conn then
conn:close();
return true;
end
return conn, err;
end
function _M.singlematch(query)
local ld = _M.getconnection();
query.sizelimit = 1;
query.scope = 'subtree';
for dn, attribs in ld:search(query) do
attribs.dn = dn;
return attribs;
end
end
_M.filter = {};
function _M.filter.combine_and(...)
local parts = { '(&' };
local arg = { ... };
for _, filter in ipairs(arg) do
if filter:sub(1, 1) ~= '(' and filter:sub(-1) ~= ')' then
filter = '(' .. filter .. ')'
end
parts[#parts + 1] = filter;
end
parts[#parts + 1] = ')';
return tconcat(parts, '');
end
do
local ok, err;
metronome.unlock_globals();
ok, ldap = pcall(require, 'lualdap');
metronome.lock_globals();
if not ok then
module:log("error", "Failed to load the LuaLDAP library for accessing LDAP: %s", ldap);
module:log("error", "More information on install LuaLDAP can be found at http://www.keplerproject.org/lualdap");
return;
end
if not params then
module:log("error", "LDAP configuration required to use the LDAP storage module");
return;
end
ok, err = validate_config();
if not ok then
module:log("error", "LDAP configuration is invalid: %s", tostring(err));
return;
end
end
return _M;

View file

@ -1,90 +0,0 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- https://github.com/YunoHost/yunohost-config-metronome/blob/unstable/lib/modules/mod_auth_ldap2.lua
-- adapted to use common LDAP store on Metronome
local ldap = module:require 'ldap';
local new_sasl = require 'util.sasl'.new;
local jsplit = require 'util.jid'.split;
local log = module._log
if not ldap then
return;
end
function new_default_provider(host)
local provider = { name = "ldap2" };
log("debug", "initializing ldap2 authentication provider for host '%s'", host);
function provider.test_password(username, password)
return ldap.bind(username, password);
end
function provider.user_exists(username)
local params = ldap.getparams()
local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
if params.user.usernamefield == 'mail' then
filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*');
end
return ldap.singlematch {
base = params.user.basedn,
filter = filter,
};
end
function provider.get_password(username)
return nil, "Passwords unavailable for LDAP.";
end
function provider.set_password(username, password)
return nil, "Passwords unavailable for LDAP.";
end
function provider.create_user(username, password)
return nil, "Account creation/modification not available with LDAP.";
end
function provider.get_sasl_handler(session)
local testpass_authentication_profile = {
session = session,
plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true;
end,
order = { "plain_test" },
};
return new_sasl(module.host, testpass_authentication_profile);
end
function provider.is_admin(jid)
local admin_config = ldap.getparams().admin;
if not admin_config then
return;
end
local ld = ldap:getconnection();
local username = jsplit(jid);
local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
return ldap.singlematch {
base = admin_config.basedn,
filter = filter,
};
end
return provider;
end
module:add_item("auth-provider", new_default_provider(module.host));

View file

@ -1,87 +0,0 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local t_concat = table.concat;
local secure_auth_only = module:get_option("c2s_require_encryption")
or module:get_option("require_encryption")
or not(module:get_option("allow_unencrypted_plain_auth"));
local sessionmanager = require "core.sessionmanager";
local usermanager = require "core.usermanager";
local nodeprep = require "util.encodings".stringprep.nodeprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
module:add_feature("jabber:iq:auth");
module:hook("stream-features", function(event)
local origin, features = event.origin, event.features;
if secure_auth_only and not origin.secure then
-- Sorry, not offering to insecure streams!
return;
elseif not origin.username then
features:tag("auth", {xmlns='http://jabber.org/features/iq-auth'}):up();
end
end);
module:hook("stanza/iq/jabber:iq:auth:query", function(event)
local session, stanza = event.origin, event.stanza;
if session.type ~= "c2s_unauthed" then
(session.sends2s or session.send)(st.error_reply(stanza, "cancel", "service-unavailable", "Legacy authentication is only allowed for unauthenticated client connections."));
return true;
end
if secure_auth_only and not session.secure then
session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server"));
return true;
end
local username = stanza.tags[1]:child_with_name("username");
local password = stanza.tags[1]:child_with_name("password");
local resource = stanza.tags[1]:child_with_name("resource");
if not (username and password and resource) then
local reply = st.reply(stanza);
session.send(reply:query("jabber:iq:auth")
:tag("username"):up()
:tag("password"):up()
:tag("resource"):up());
else
username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
username = nodeprep(username);
resource = resourceprep(resource)
if not (username and resource) then
session.send(st.error_reply(stanza, "modify", "bad-request"));
return true;
end
if usermanager.test_password(username, session.host, password) then
-- Authentication successful!
local success, err = sessionmanager.make_authenticated(session, username);
if success then
local err_type, err_msg;
success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource);
if not success then
session.send(st.error_reply(stanza, err_type, err, err_msg));
session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager?
return true;
elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth
session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session."));
session:close(); -- FIXME undo resource bind and auth instead of closing the session?
return true;
end
end
session.send(st.reply(stanza));
else
session.send(st.error_reply(stanza, "auth", "not-authorized"));
end
end
return true;
end);

View file

@ -1,243 +0,0 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
----------------------------------------
-- Constants and such --
----------------------------------------
local setmetatable = setmetatable;
local get_config = require "core.configmanager".get;
local ldap = module:require 'ldap';
local vcardlib = module:require 'vcard';
local st = require 'util.stanza';
local gettime = require 'socket'.gettime;
local log = module._log
if not ldap then
return;
end
local CACHE_EXPIRY = 300;
----------------------------------------
-- Utility Functions --
----------------------------------------
local function ldap_record_to_vcard(record, format)
return vcardlib.create {
record = record,
format = format,
}
end
local get_alias_for_user;
do
local user_cache;
local last_fetch_time;
local function populate_user_cache()
local user_c = get_config(module.host, 'ldap').user;
if not user_c then return; end
local ld = ldap.getconnection();
local usernamefield = user_c.usernamefield;
local namefield = user_c.namefield;
user_cache = {};
for _, attrs in ld:search { base = user_c.basedn, scope = 'onelevel', filter = user_c.filter } do
user_cache[attrs[usernamefield]] = attrs[namefield];
end
last_fetch_time = gettime();
end
function get_alias_for_user(user)
if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then
user_cache = nil;
end
if not user_cache then
populate_user_cache();
end
return user_cache[user];
end
end
----------------------------------------
-- Base LDAP store class --
----------------------------------------
local function ldap_store(config)
local self = {};
local config = config;
function self:get(username)
return nil, "Data getting is not available for this storage backend";
end
function self:set(username, data)
return nil, "Data setting is not available for this storage backend";
end
return self;
end
local adapters = {};
----------------------------------------
-- Roster Storage Implementation --
----------------------------------------
adapters.roster = function (config)
-- Validate configuration requirements
if not config.groups then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local contacts = {};
local memberfield = config.groups.memberfield;
local namefield = config.groups.namefield;
local filter = memberfield .. '=' .. tostring(username);
local groups = {};
for _, config in ipairs(config.groups) do
groups[ config[namefield] ] = config.name;
end
log("debug", "Found %d group(s) for user %s", select('#', groups), username)
-- XXX this kind of relies on the way we do groups at INOC
for _, attrs in ld:search { base = config.groups.basedn, scope = 'onelevel', filter = filter } do
if groups[ attrs[namefield] ] then
local members = attrs[memberfield];
for _, user in ipairs(members) do
if user ~= username then
local jid = user .. '@' .. module.host;
local record = contacts[jid];
if not record then
record = {
subscription = 'both',
groups = {},
name = get_alias_for_user(user),
};
contacts[jid] = record;
end
record.groups[ groups[ attrs[namefield] ] ] = true;
end
end
end
end
return contacts;
end
function self:set(username, data)
log("warn", "Setting data in Roster LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- vCard Storage Implementation --
----------------------------------------
adapters.vcard = function (config)
-- Validate configuration requirements
if not config.vcard_format or not config.user then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local filter = config.user.usernamefield .. '=' .. tostring(username);
log("debug", "Retrieving vCard for user '%s'", username);
local match = ldap.singlematch {
base = config.user.basedn,
filter = filter,
};
if match then
match.jid = username .. '@' .. module.host
return st.preserialize(ldap_record_to_vcard(match, config.vcard_format));
else
return nil, "username not found";
end
end
function self:set(username, data)
log("warn", "Setting data in vCard LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- Driver Definition --
----------------------------------------
cache = {};
local driver = { name = "ldap" };
function driver:open(store)
log("debug", "Opening ldap storage backend for host '%s' and store '%s'", module.host, store);
if not cache[module.host] then
log("debug", "Caching adapters for the host '%s'", module.host);
local ad_config = get_config(module.host, "ldap");
local ad_cache = {};
for k, v in pairs(adapters) do
ad_cache[k] = v(ad_config);
end
cache[module.host] = ad_cache;
end
local adapter = cache[module.host][store];
if not adapter then
log("info", "Unavailable adapter for store '%s'", store);
return nil, "unsupported-store";
end
return adapter;
end
function driver:stores(username, type, pattern)
return nil, "not implemented";
end
function driver:store_exists(username, type)
return nil, "not implemented";
end
function driver:purge(username)
return nil, "not implemented";
end
function driver:nodes(type)
return nil, "not implemented";
end
module:add_item("data-driver", driver);

View file

@ -1,162 +0,0 @@
-- vim:sts=4 sw=4
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require 'util.stanza';
local VCARD_NS = 'vcard-temp';
local builder_methods = {};
local base64_encode = require('util.encodings').base64.encode;
function builder_methods:addvalue(key, value)
self.vcard:tag(key):text(value):up();
end
function builder_methods:addphotofield(tagname, format_section)
local record = self.record;
local format = self.format;
local vcard = self.vcard;
local config = format[format_section];
if not config then
return;
end
if config.extval then
if record[config.extval] then
local tag = vcard:tag(tagname);
tag:tag('EXTVAL'):text(record[config.extval]):up();
end
elseif config.type and config.binval then
if record[config.binval] then
local tag = vcard:tag(tagname);
tag:tag('TYPE'):text(config.type):up();
tag:tag('BINVAL'):text(base64_encode(record[config.binval])):up();
end
else
module:log('error', 'You have an invalid %s config section', tagname);
return;
end
vcard:up();
end
function builder_methods:addregularfield(tagname, format_section)
local record = self.record;
local format = self.format;
local vcard = self.vcard;
if not format[format_section] then
return;
end
local tag = vcard:tag(tagname);
for k, v in pairs(format[format_section]) do
tag:tag(string.upper(k)):text(record[v]):up();
end
vcard:up();
end
function builder_methods:addmultisectionedfield(tagname, format_section)
local record = self.record;
local format = self.format;
local vcard = self.vcard;
if not format[format_section] then
return;
end
for k, v in pairs(format[format_section]) do
local tag = vcard:tag(tagname);
if type(k) == 'string' then
tag:tag(string.upper(k)):up();
end
for k2, v2 in pairs(v) do
if type(v2) == 'boolean' then
tag:tag(string.upper(k2)):up();
else
tag:tag(string.upper(k2)):text(record[v2]):up();
end
end
vcard:up();
end
end
function builder_methods:build()
local record = self.record;
local format = self.format;
self:addvalue( 'VERSION', '2.0');
self:addvalue( 'FN', record[format.displayname]);
self:addregularfield( 'N', 'name');
self:addvalue( 'NICKNAME', record[format.nickname]);
self:addphotofield( 'PHOTO', 'photo');
self:addvalue( 'BDAY', record[format.birthday]);
self:addmultisectionedfield('ADR', 'address');
self:addvalue( 'LABEL', nil); -- we don't support LABEL...yet.
self:addmultisectionedfield('TEL', 'telephone');
self:addmultisectionedfield('EMAIL', 'email');
self:addvalue( 'JABBERID', record.jid);
self:addvalue( 'MAILER', record[format.mailer]);
self:addvalue( 'TZ', record[format.timezone]);
self:addregularfield( 'GEO', 'geo');
self:addvalue( 'TITLE', record[format.title]);
self:addvalue( 'ROLE', record[format.role]);
self:addphotofield( 'LOGO', 'logo');
self:addvalue( 'AGENT', nil); -- we don't support AGENT...yet.
self:addregularfield( 'ORG', 'org');
self:addvalue( 'CATEGORIES', nil); -- we don't support CATEGORIES...yet.
self:addvalue( 'NOTE', record[format.note]);
self:addvalue( 'PRODID', nil); -- we don't support PRODID...yet.
self:addvalue( 'REV', record[format.rev]);
self:addvalue( 'SORT-STRING', record[format.sortstring]);
self:addregularfield( 'SOUND', 'sound');
self:addvalue( 'UID', record[format.uid]);
self:addvalue( 'URL', record[format.url]);
self:addvalue( 'CLASS', nil); -- we don't support CLASS...yet.
self:addregularfield( 'KEY', 'key');
self:addvalue( 'DESC', record[format.description]);
return self.vcard;
end
local function new_builder(params)
local vcard_tag = st.stanza('vCard', { xmlns = VCARD_NS });
local object = {
vcard = vcard_tag,
__index = builder_methods,
};
for k, v in pairs(params) do
object[k] = v;
end
setmetatable(object, object);
return object;
end
local _M = {};
function _M.create(params)
local builder = new_builder(params);
return builder:build();
end
return _M;

View file

@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade {
server {
listen 80;
listen [::]:80;
server_name {{ domain }}{% if xmpp_enabled == "True" %} xmpp-upload.{{ domain }} muc.{{ domain }}{% endif %};
server_name {{ domain }};
access_by_lua_file /usr/share/ssowat/access.lua;
@ -78,48 +78,3 @@ server {
access_log /var/log/nginx/{{ domain }}-access.log;
error_log /var/log/nginx/{{ domain }}-error.log;
}
{% if xmpp_enabled == "True" %}
# vhost dedicated to XMPP http_upload
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name xmpp-upload.{{ domain }};
root /dev/null;
location /upload/ {
alias /var/xmpp-upload/{{ domain }}/upload/;
# Pass all requests to metronome, except for GET and HEAD requests.
limit_except GET HEAD {
proxy_pass http://localhost:5290;
}
include proxy_params;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'HEAD, GET, PUT, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
client_max_body_size 105M; # Choose a value a bit higher than the max upload configured in XMPP server
}
include /etc/nginx/conf.d/security.conf.inc;
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem;
{% if domain_cert_ca != "selfsigned" %}
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
{% endif %}
{% if domain_cert_ca == "letsencrypt" %}
# OCSP settings
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
resolver 1.1.1.1 9.9.9.9 valid=300s;
resolver_timeout 5s;
{% endif %}
access_log /var/log/nginx/xmpp-upload.{{ domain }}-access.log;
error_log /var/log/nginx/xmpp-upload.{{ domain }}-error.log;
}
{% endif %}

View file

@ -56,7 +56,6 @@ objectClass: groupOfNamesYnh
gidNumber: 4002
cn: all_users
permission: cn=mail.main,ou=permission,dc=yunohost,dc=org
permission: cn=xmpp.main,ou=permission,dc=yunohost,dc=org
dn: cn=visitors,ou=groups,dc=yunohost,dc=org
objectClass: posixGroup
@ -75,17 +74,6 @@ gidNumber: 5001
showTile: FALSE
authHeader: FALSE
dn: cn=xmpp.main,ou=permission,dc=yunohost,dc=org
groupPermission: cn=all_users,ou=groups,dc=yunohost,dc=org
cn: xmpp.main
objectClass: posixGroup
objectClass: permissionYnh
isProtected: TRUE
label: XMPP
gidNumber: 5002
showTile: FALSE
authHeader: FALSE
dn: cn=ssh.main,ou=permission,dc=yunohost,dc=org
cn: ssh.main
objectClass: posixGroup

View file

@ -192,7 +192,7 @@ authorityKeyIdentifier=keyid,issuer
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org,DNS:xmpp-upload.yunohost.org
subjectAltName=DNS:yunohost.org,DNS:www.yunohost.org,DNS:ns.yunohost.org
[ v3_ca ]

View file

@ -8,11 +8,6 @@ fail2ban:
log: /var/log/fail2ban.log
category: security
test_conf: fail2ban-server --test
metronome:
log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err]
needs_exposed_ports: [5222, 5269]
category: xmpp
ignore_if_package_is_not_installed: metronome
mysql:
log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log]
actual_systemd_service: mariadb

1
debian/install vendored
View file

@ -6,5 +6,4 @@ conf/* /usr/share/yunohost/conf/
locales/* /usr/share/yunohost/locales/
doc/yunohost.8.gz /usr/share/man/man8/
doc/bash_completion.d/* /etc/bash_completion.d/
conf/metronome/modules/* /usr/lib/metronome/modules/
src/* /usr/lib/python3/dist-packages/yunohost/

View file

@ -1,13 +0,0 @@
#!/bin/bash
# Exit hook on subcommand error or unset variable
set -eu
# Source YNH helpers
source /usr/share/yunohost/helpers
# Backup destination
backup_dir="${1}/data/xmpp"
[[ ! -d /var/lib/metronome ]] || ynh_backup /var/lib/metronome "${backup_dir}/var_lib_metronome" --not_mandatory
[[ ! -d /var/xmpp-upload ]] || ynh_backup /var/xmpp-upload/ "${backup_dir}/var_xmpp-upload" --not_mandatory

View file

@ -1,95 +0,0 @@
#!/bin/bash
set -e
if ! dpkg --list | grep -q 'ii *metronome '
then
echo 'metronome is not installed, skipping'
exit 0
fi
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/conf/metronome
# create directories for pending conf
metronome_dir="${pending_dir}/etc/metronome"
metronome_conf_dir="${metronome_dir}/conf.d"
mkdir -p "$metronome_conf_dir"
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
# install main conf file
cat metronome.cfg.lua \
| sed "s/{{ main_domain }}/${main_domain}/g" \
>"${metronome_dir}/metronome.cfg.lua"
# Trick such that old conf files are flagged as to remove
for domain in $YNH_DOMAINS; do
touch "${metronome_conf_dir}/${domain}.cfg.lua"
done
# add domain conf files
domain_list="$(yunohost domain list --features xmpp --output-as json | jq -r ".domains[]")"
for domain in $domain_list; do
cat domain.tpl.cfg.lua \
| sed "s/{{ domain }}/${domain}/g" \
>"${metronome_conf_dir}/${domain}.cfg.lua"
done
# remove old domain conf files
conf_files=$(ls -1 /etc/metronome/conf.d \
| awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }')
for file in $conf_files; do
domain=${file%.cfg.lua}
[[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${metronome_conf_dir}/${file}"
done
}
do_post_regen() {
regen_conf_files=$1
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
# create metronome directories for domains
for domain in $YNH_MAIN_DOMAINS; do
mkdir -p "/var/lib/metronome/${domain//./%2e}/pep"
# http_upload directory must be writable by metronome and readable by nginx
mkdir -p "/var/xmpp-upload/${domain}/upload"
# sgid bit allows that file created in that dir will be owned by www-data
# despite the fact that metronome ain't in the www-data group
chmod g+s "/var/xmpp-upload/${domain}/upload"
done
# fix some permissions
[ ! -e '/var/xmpp-upload' ] || chown -R metronome:www-data "/var/xmpp-upload/"
[ ! -e '/var/xmpp-upload' ] || chmod 750 "/var/xmpp-upload/"
# metronome should be in ssl-cert group to let it access SSL certificates
usermod -aG ssl-cert metronome
chown -R metronome: /var/lib/metronome/
chown -R metronome: /etc/metronome/conf.d/
if [[ -z "$(ls /etc/metronome/conf.d/*.cfg.lua 2>/dev/null)" ]]
then
if systemctl is-enabled metronome &>/dev/null
then
systemctl disable metronome --now 2>/dev/null
fi
else
if ! systemctl is-enabled metronome &>/dev/null
then
systemctl enable metronome --now 2>/dev/null
sleep 3
fi
[[ -z "$regen_conf_files" ]] \
|| systemctl restart metronome
fi
}
do_$1_regen ${@:2}

View file

@ -74,7 +74,6 @@ do_pre_regen() {
cert_status=$(yunohost domain cert status --json)
# add domain conf files
xmpp_domain_list="$(yunohost domain list --features xmpp --output-as json | jq -r ".domains[]")"
mail_domain_list="$(yunohost domain list --features mail_in mail_out --output-as json | jq -r ".domains[]")"
for domain in $YNH_DOMAINS; do
domain_conf_dir="${nginx_conf_dir}/${domain}.d"
@ -87,12 +86,6 @@ do_pre_regen() {
export domain_cert_ca=$(echo $cert_status \
| jq ".certificates.\"$domain\".CA_type" \
| tr -d '"')
if echo "$xmpp_domain_list" | grep -q "^$domain$"
then
export xmpp_enabled="True"
else
export xmpp_enabled="False"
fi
if echo "$mail_domain_list" | grep -q "^$domain$"
then
export mail_enabled="True"

View file

@ -1,11 +0,0 @@
backup_dir="$1/data/xmpp"
if [[ -e $backup_dir/var_lib_metronome/ ]]
then
cp -a $backup_dir/var_lib_metronome/. /var/lib/metronome
fi
if [[ -e $backup_dir/var_xmpp-upload ]]
then
cp -a $backup_dir/var_xmpp-upload/. /var/xmpp-upload
fi

View file

@ -93,7 +93,7 @@
"ask_new_domain": "New domain",
"ask_new_path": "New path",
"ask_password": "Password",
"ask_user_domain": "Domain to use for the user's email address and XMPP account",
"ask_user_domain": "Domain to use for the user's email address",
"backup_abstract_method": "This backup method has yet to be implemented",
"backup_actually_backuping": "Creating a backup archive from the collected files…",
"backup_app_failed": "Could not back up {app}",
@ -330,8 +330,6 @@
"diagnosis_using_yunohost_testing_details": "This is probably OK if you know what you are doing, but pay attention to the release notes before installing YunoHost upgrades! If you want to disable 'testing' upgrades, you should remove the <cmd>testing</cmd> keyword from <cmd>/etc/apt/sources.list.d/yunohost.list</cmd>.",
"disk_space_not_sufficient_install": "There is not enough disk space left to install this application",
"disk_space_not_sufficient_update": "There is not enough disk space left to update this application",
"domain_cannot_add_muc_upload": "You cannot add domains starting with 'muc.'. This kind of name is reserved for the XMPP multi-users chat feature integrated into YunoHost.",
"domain_cannot_add_xmpp_upload": "You cannot add domains starting with 'xmpp-upload.'. This kind of name is reserved for the XMPP upload feature integrated into YunoHost.",
"domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n <another-domain>'; here is the list of candidate domains: {other_domains}",
"domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add <another-domain.com>', then set is as the main domain using 'yunohost domain main-domain -n <another-domain.com>' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.",
"domain_cert_gen_failed": "Could not generate certificate",
@ -374,8 +372,6 @@
"domain_config_search_engine_help": "An URL with an empty query string like `https://duckduckgo.com/?q=`, with `q=` as duckduckgo's empty query parameter",
"domain_config_search_engine_name": "Search engine name",
"domain_config_show_other_domains_apps": "Show other domain's apps",
"domain_config_xmpp": "Instant messaging (XMPP)",
"domain_config_xmpp_help": "NB: some XMPP features will require that you update your DNS records and regenerate your Lets Encrypt certificate to be enabled",
"domain_created": "Domain created",
"domain_creation_failed": "Unable to create domain {domain}: {error}",
"domain_deleted": "Domain deleted",
@ -750,7 +746,6 @@
"service_description_dnsmasq": "Handles domain name resolution (DNS)",
"service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)",
"service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet",
"service_description_metronome": "Manage XMPP instant messaging accounts",
"service_description_mysql": "Stores app data (SQL database)",
"service_description_nginx": "Serves or provides access to all the websites hosted on your server",
"service_description_postfix": "Used to send and receive e-mails",

View file

@ -86,7 +86,7 @@ user:
comment: good_practices_about_user_password
-d:
full: --domain
help: Domain for the email address and xmpp account
help: Domain for the email address
extra:
pattern: &pattern_domain
- !!str ^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$
@ -467,7 +467,7 @@ domain:
help: Display domains as a tree
action: store_true
--features:
help: List only domains with features enabled (xmpp, mail_in, mail_out)
help: List only domains with features enabled (mail_in, mail_out)
nargs: "*"
### domain_info()
@ -1982,7 +1982,7 @@ diagnosis:
api: PUT /diagnosis/ignore
arguments:
--filter:
help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=xmpp'"
help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=mail'"
nargs: "*"
metavar: CRITERIA
--list:

View file

@ -60,12 +60,6 @@ name = "Features"
type = "boolean"
default = 1
[feature.xmpp]
[feature.xmpp.xmpp]
type = "boolean"
default = 0
[dns]
name = "DNS"

View file

@ -556,6 +556,7 @@ def _fetch_and_enable_new_certificate(domain, no_checks=False):
def _prepare_certificate_signing_request(domain, key_file, output_folder):
from OpenSSL import crypto # lazy loading this module for performance reasons
from yunohost.hook import hook_callback
# Init a request
csr = crypto.X509Req()
@ -563,53 +564,34 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder):
# Set the domain
csr.get_subject().CN = domain
from yunohost.domain import domain_config_get
sanlist = []
hook_results = hook_callback("cert_alternate_names", env={"domain": domain})
for hook_name, results in hook_results.items():
#
# There can be multiple results per hook name, so results look like
# {'/some/path/to/hook1':
# { 'state': 'succeed',
# 'stdreturn': ["foo", "bar"]
# },
# '/some/path/to/hook2':
# { ... },
# [...]
#
# Loop over the sub-results
for result in results.values():
if results["stdreturn"]:
sanlist += results["stdreturn"]
# If XMPP is enabled for this domain, add xmpp-upload and muc subdomains
# in subject alternate names
if domain_config_get(domain, key="feature.xmpp.xmpp") == 1:
subdomain = "xmpp-upload." + domain
xmpp_records = (
Diagnoser.get_cached_report(
"dnsrecords", item={"domain": domain, "category": "xmpp"}
).get("data")
or {}
)
sanlist = []
# Handle the boring case where the domain is not the root of the dns zone etc...
from yunohost.dns import (
_get_relative_name_for_dns_zone,
_get_dns_zone_for_domain,
)
base_dns_zone = _get_dns_zone_for_domain(domain)
basename = _get_relative_name_for_dns_zone(domain, base_dns_zone)
suffix = f".{basename}" if basename != "@" else ""
for sub in ("xmpp-upload", "muc"):
subdomain = sub + "." + domain
if xmpp_records.get("CNAME:" + sub + suffix) == "OK":
sanlist.append(("DNS:" + subdomain))
else:
logger.warning(
m18n.n(
"certmanager_warning_subdomain_dns_record",
subdomain=subdomain,
domain=domain,
)
if sanlist:
csr.add_extensions(
[
crypto.X509Extension(
b"subjectAltName",
False,
(", ".join(["DNS:{sub}.{domain}" for sub in sanlist])).encode("utf-8"),
)
if sanlist:
csr.add_extensions(
[
crypto.X509Extension(
b"subjectAltName",
False,
(", ".join(sanlist)).encode("utf-8"),
)
]
)
]
)
# Set the key
with open(key_file, "rt") as f:

View file

@ -91,7 +91,7 @@ class MyDiagnoser(Diagnoser):
domain, include_empty_AAAA_if_no_ipv6=True
)
categories = ["basic", "mail", "xmpp", "extra"]
categories = ["basic", "mail", "extra"]
for category in categories:
records = expected_configuration[category]

View file

@ -75,12 +75,6 @@ def domain_dns_suggest(domain):
result += "\n{name} {ttl} IN {type} {value}".format(**record)
result += "\n\n"
if dns_conf["xmpp"]:
result += "\n\n"
result += "; XMPP"
for record in dns_conf["xmpp"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record)
if dns_conf["extra"]:
result += "\n\n"
result += "; Extra"
@ -88,7 +82,7 @@ def domain_dns_suggest(domain):
result += "\n{name} {ttl} IN {type} {value}".format(**record)
for name, record_list in dns_conf.items():
if name not in ("basic", "xmpp", "mail", "extra") and record_list:
if name not in ("basic", "mail", "extra") and record_list:
result += "\n\n"
result += "; " + name
for record in record_list:
@ -117,14 +111,6 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
# if ipv6 available
{"type": "AAAA", "name": "@", "value": "valid-ipv6", "ttl": 3600},
],
"xmpp": [
{"type": "SRV", "name": "_xmpp-client._tcp", "value": "0 5 5222 domain.tld.", "ttl": 3600},
{"type": "SRV", "name": "_xmpp-server._tcp", "value": "0 5 5269 domain.tld.", "ttl": 3600},
{"type": "CNAME", "name": "muc", "value": "@", "ttl": 3600},
{"type": "CNAME", "name": "pubsub", "value": "@", "ttl": 3600},
{"type": "CNAME", "name": "vjud", "value": "@", "ttl": 3600}
{"type": "CNAME", "name": "xmpp-upload", "value": "@", "ttl": 3600}
],
"mail": [
{"type": "MX", "name": "@", "value": "10 domain.tld.", "ttl": 3600},
{"type": "TXT", "name": "@", "value": "\"v=spf1 a mx ip4:123.123.123.123 ipv6:valid-ipv6 -all\"", "ttl": 3600 },
@ -148,7 +134,6 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
basic = []
mail = []
xmpp = []
extra = []
ipv4 = get_public_ip()
ipv6 = get_public_ip(6)
@ -211,29 +196,6 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
[f"_dmarc{suffix}", ttl, "TXT", '"v=DMARC1; p=none"'],
]
########
# XMPP #
########
if settings["xmpp"]:
xmpp += [
[
f"_xmpp-client._tcp{suffix}",
ttl,
"SRV",
f"0 5 5222 {domain}.",
],
[
f"_xmpp-server._tcp{suffix}",
ttl,
"SRV",
f"0 5 5269 {domain}.",
],
[f"muc{suffix}", ttl, "CNAME", f"{domain}."],
[f"pubsub{suffix}", ttl, "CNAME", f"{domain}."],
[f"vjud{suffix}", ttl, "CNAME", f"{domain}."],
[f"xmpp-upload{suffix}", ttl, "CNAME", f"{domain}."],
]
#########
# Extra #
#########
@ -259,10 +221,6 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
{"name": name, "ttl": ttl_, "type": type_, "value": value}
for name, ttl_, type_, value in basic
],
"xmpp": [
{"name": name, "ttl": ttl_, "type": type_, "value": value}
for name, ttl_, type_, value in xmpp
],
"mail": [
{"name": name, "ttl": ttl_, "type": type_, "value": value}
for name, ttl_, type_, value in mail
@ -277,15 +235,8 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
# Custom records #
##################
# Defined by custom hooks ships in apps for example ...
# FIXME : this ain't practical for apps that may want to add
# custom dns records for a subdomain ... there's no easy way for
# an app to compare the base domain is the parent of the subdomain ?
# (On the other hand, in sep 2021, it looks like no app is using
# this mechanism...)
hook_results = hook_callback("custom_dns_rules", args=[base_domain])
# Defined by custom hooks shipped in apps for example ...
hook_results = hook_callback("custom_dns_rules", env={"base_domain": base_domain, "suffix": suffix})
for hook_name, results in hook_results.items():
#
# There can be multiple results per hook name, so results look like

View file

@ -264,12 +264,6 @@ def domain_add(
if dyndns_recovery_password:
operation_logger.data_to_redact.append(dyndns_recovery_password)
if domain.startswith("xmpp-upload."):
raise YunohostValidationError("domain_cannot_add_xmpp_upload")
if domain.startswith("muc."):
raise YunohostError("domain_cannot_add_muc_upload")
ldap = _get_ldap_interface()
try:
@ -338,7 +332,6 @@ def domain_add(
regen_conf(
names=[
"nginx",
"metronome",
"dnsmasq",
"postfix",
"rspamd",
@ -512,7 +505,7 @@ def domain_remove(
f"/etc/nginx/conf.d/{domain}.conf", new_conf=None, save=True
)
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"])
regen_conf(names=["nginx", "dnsmasq", "postfix", "rspamd", "mdns"])
app_ssowatconf()
hook_callback("post_domain_remove", args=[domain])
@ -686,7 +679,6 @@ def _get_DomainConfigPanel():
# i18n: domain_config_cert_renew_help
# i18n: domain_config_default_app_help
# i18n: domain_config_xmpp_help
def _get_raw_config(self) -> "RawConfig":
# TODO add mechanism to share some settings with other domains on the same zone
@ -695,10 +687,6 @@ def _get_DomainConfigPanel():
any_filter = all(self.filter_key)
panel_id, section_id, option_id = self.filter_key
raw_config["feature"]["xmpp"]["xmpp"]["default"] = (
1 if self.entity == _get_maindomain() else 0
)
# Portal settings are only available on "topest" domains
if _get_parent_domain_of(self.entity, topest=True) is not None:
del raw_config["feature"]["portal"]
@ -835,9 +823,6 @@ def _get_DomainConfigPanel():
app_ssowatconf()
stuff_to_regen_conf = set()
if "xmpp" in next_settings:
stuff_to_regen_conf.update({"nginx", "metronome"})
if "mail_in" in next_settings or "mail_out" in next_settings:
stuff_to_regen_conf.update({"nginx", "postfix", "dovecot", "rspamd"})

View file

@ -471,7 +471,7 @@ def dyndns_update(
# Delete custom DNS records, we don't support them (have to explicitly
# authorize them on dynette)
for category in dns_conf.keys():
if category not in ["basic", "mail", "xmpp", "extra"]:
if category not in ["basic", "mail", "extra"]:
del dns_conf[category]
# Delete the old records for all domain/subdomains

View file

@ -28,7 +28,7 @@ from yunohost.log import is_unit_operation
logger = getLogger("yunohost.user")
SYSTEM_PERMS = ["mail", "xmpp", "sftp", "ssh"]
SYSTEM_PERMS = ["mail", "sftp", "ssh"]
#
#
@ -170,7 +170,7 @@ def user_permission_update(
existing_permission = user_permission_info(permission)
# Refuse to add "visitors" to mail, xmpp ... they require an account to make sense.
# Refuse to add "visitors" to mail ... they require an account to make sense.
if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS:
raise YunohostValidationError(
"permission_require_account", permission=permission

View file

@ -705,10 +705,6 @@ def _get_services():
"category": "web",
}
# Ignore metronome entirely if XMPP was disabled on all domains
if "metronome" in services and not glob("/etc/metronome/conf.d/*.cfg.lua"):
del services["metronome"]
# Remove legacy /var/log/daemon.log and /var/log/syslog from log entries
# because they are too general. Instead, now the journalctl log is
# returned by default which is more relevant.

View file

@ -167,7 +167,7 @@ def user_create(
assert_password_is_compatible(password)
assert_password_is_strong_enough("admin" if admin else "user", password)
# Validate domain used for email address/xmpp account
# Validate domain used for email address account
if domain is None:
if Moulinette.interface.type == "api":
raise YunohostValidationError(