mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #815 from YunoHost/allow-system-user-to-auth-on-mail-stack
Allow system users to auth on the mail stack and send emails
This commit is contained in:
commit
961dc5a6ee
6 changed files with 100 additions and 4 deletions
|
@ -38,14 +38,26 @@ ssl_prefer_server_ciphers = no
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# Regular Yunohost accounts
|
||||||
passdb {
|
passdb {
|
||||||
args = /etc/dovecot/dovecot-ldap.conf
|
args = /etc/dovecot/dovecot-ldap.conf
|
||||||
driver = ldap
|
driver = ldap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Internally, allow authentication from apps system user who have "enable_email = true"
|
||||||
|
passdb {
|
||||||
|
driver = passwd-file
|
||||||
|
args = /etc/dovecot/app-senders-passwd
|
||||||
|
}
|
||||||
|
|
||||||
userdb {
|
userdb {
|
||||||
args = /etc/dovecot/dovecot-ldap.conf
|
|
||||||
driver = ldap
|
driver = ldap
|
||||||
|
args = /etc/dovecot/dovecot-ldap.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
userdb {
|
||||||
|
driver = passwd-file
|
||||||
|
args = /etc/dovecot/app-senders-passwd
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol imap {
|
protocol imap {
|
||||||
|
|
|
@ -107,7 +107,12 @@ virtual_alias_domains =
|
||||||
virtual_minimum_uid = 100
|
virtual_minimum_uid = 100
|
||||||
virtual_uid_maps = static:vmail
|
virtual_uid_maps = static:vmail
|
||||||
virtual_gid_maps = static:mail
|
virtual_gid_maps = static:mail
|
||||||
smtpd_sender_login_maps= ldap:/etc/postfix/ldap-accounts.cf
|
smtpd_sender_login_maps=
|
||||||
|
# Regular Yunohost accounts
|
||||||
|
ldap:/etc/postfix/ldap-accounts.cf,
|
||||||
|
# Extra maps for app system users who need to send emails
|
||||||
|
hash:/etc/postfix/app_senders_login_maps
|
||||||
|
|
||||||
|
|
||||||
# Dovecot LDA
|
# Dovecot LDA
|
||||||
virtual_transport = dovecot
|
virtual_transport = dovecot
|
||||||
|
|
|
@ -80,6 +80,8 @@ do_post_regen() {
|
||||||
|
|
||||||
postmap -F hash:/etc/postfix/sni
|
postmap -F hash:/etc/postfix/sni
|
||||||
|
|
||||||
|
python3 -c 'from yunohost.app import regen_mail_app_user_config_for_dovecot_and_postfix as r; r(only="postfix")'
|
||||||
|
|
||||||
[[ -z "$regen_conf_files" ]] \
|
[[ -z "$regen_conf_files" ]] \
|
||||||
|| { systemctl restart postfix && systemctl restart postsrsd; }
|
|| { systemctl restart postfix && systemctl restart postsrsd; }
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ do_post_regen() {
|
||||||
chown root:mail /var/mail
|
chown root:mail /var/mail
|
||||||
chmod 1775 /var/mail
|
chmod 1775 /var/mail
|
||||||
|
|
||||||
|
python3 -c 'from yunohost.app import regen_mail_app_user_config_for_dovecot_and_postfix as r; r(only="dovecot")'
|
||||||
|
|
||||||
[ -z "$regen_conf_files" ] && exit 0
|
[ -z "$regen_conf_files" ] && exit 0
|
||||||
|
|
||||||
# compile sieve script
|
# compile sieve script
|
||||||
|
|
47
src/app.py
47
src/app.py
|
@ -1636,6 +1636,9 @@ def app_setting(app, key, value=None, delete=False):
|
||||||
if delete:
|
if delete:
|
||||||
if key in app_settings:
|
if key in app_settings:
|
||||||
del app_settings[key]
|
del app_settings[key]
|
||||||
|
else:
|
||||||
|
# Don't call _set_app_settings to avoid unecessary writes...
|
||||||
|
return
|
||||||
|
|
||||||
# SET
|
# SET
|
||||||
else:
|
else:
|
||||||
|
@ -3234,3 +3237,47 @@ def _ask_confirmation(
|
||||||
|
|
||||||
if not answer:
|
if not answer:
|
||||||
raise YunohostError("aborting")
|
raise YunohostError("aborting")
|
||||||
|
|
||||||
|
|
||||||
|
def regen_mail_app_user_config_for_dovecot_and_postfix(only=None):
|
||||||
|
|
||||||
|
dovecot = True if only in [None, "dovecot"] else False
|
||||||
|
postfix = True if only in [None, "postfix"] else False
|
||||||
|
|
||||||
|
from yunohost.user import _hash_user_password
|
||||||
|
|
||||||
|
postfix_map = []
|
||||||
|
dovecot_passwd = []
|
||||||
|
for app in _installed_apps():
|
||||||
|
|
||||||
|
settings = _get_app_settings(app)
|
||||||
|
|
||||||
|
if "domain" not in settings or "mail_pwd" not in settings:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dovecot:
|
||||||
|
hashed_password = _hash_user_password(settings["mail_pwd"])
|
||||||
|
dovecot_passwd.append(f"{app}:{hashed_password}::::::allow_nets=127.0.0.1/24")
|
||||||
|
if postfix:
|
||||||
|
mail_user = settings.get("mail_user", app)
|
||||||
|
mail_domain = settings.get("mail_domain", settings["domain"])
|
||||||
|
postfix_map.append(f"{mail_user}@{mail_domain} {app}")
|
||||||
|
|
||||||
|
if dovecot:
|
||||||
|
app_senders_passwd = "/etc/dovecot/app-senders-passwd"
|
||||||
|
content = "# This file is regenerated automatically.\n# Please DO NOT edit manually ... changes will be overwritten!"
|
||||||
|
content += '\n' + '\n'.join(dovecot_passwd)
|
||||||
|
write_to_file(app_senders_passwd, content)
|
||||||
|
chmod(app_senders_passwd, 0o440)
|
||||||
|
chown(app_senders_passwd, "root", "dovecot")
|
||||||
|
|
||||||
|
if postfix:
|
||||||
|
app_senders_map = "/etc/postfix/app_senders_login_maps"
|
||||||
|
content = "# This file is regenerated automatically.\n# Please DO NOT edit manually ... changes will be overwritten!"
|
||||||
|
content += '\n' + '\n'.join(postfix_map)
|
||||||
|
write_to_file(app_senders_map, content)
|
||||||
|
chmod(app_senders_map, 0o440)
|
||||||
|
chown(app_senders_map, "postfix", "root")
|
||||||
|
os.system(f"postmap {app_senders_map} 2>/dev/null")
|
||||||
|
chmod(app_senders_map + ".db", 0o640)
|
||||||
|
chown(app_senders_map + ".db", "postfix", "root")
|
||||||
|
|
|
@ -25,6 +25,7 @@ import subprocess
|
||||||
from typing import Dict, Any, List, Union
|
from typing import Dict, Any, List, Union
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
|
from moulinette.utils.text import random_ascii
|
||||||
from moulinette.utils.process import check_output
|
from moulinette.utils.process import check_output
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.filesystem import mkdir, chown, chmod, write_to_file
|
from moulinette.utils.filesystem import mkdir, chown, chmod, write_to_file
|
||||||
|
@ -679,6 +680,7 @@ class SystemuserAppResource(AppResource):
|
||||||
##### Properties
|
##### Properties
|
||||||
- `allow_ssh`: (default: False) Adds the user to the ssh.app group, allowing SSH connection via this user
|
- `allow_ssh`: (default: False) Adds the user to the ssh.app group, allowing SSH connection via this user
|
||||||
- `allow_sftp`: (default: False) Adds the user to the sftp.app group, allowing SFTP connection via this user
|
- `allow_sftp`: (default: False) Adds the user to the sftp.app group, allowing SFTP connection via this user
|
||||||
|
- `allow_email`: (default: False) Enable authentication on the mail stack for the system user and send mail using `__APP__@__DOMAIN__`. A `mail_pwd` setting is automatically defined (similar to `db_pwd` for databases). You can then configure the app to use `__APP__` and `__MAIL_PWD__` as SMTP credentials (with host 127.0.0.1). You can also tweak the user-part of the domain-part of the email used by manually defining a custom setting `mail_user` or `mail_domain`
|
||||||
- `home`: (default: `/var/www/__APP__`) Defines the home property for this user. NB: unfortunately you can't simply use `__INSTALL_DIR__` or `__DATA_DIR__` for now
|
- `home`: (default: `/var/www/__APP__`) Defines the home property for this user. NB: unfortunately you can't simply use `__INSTALL_DIR__` or `__DATA_DIR__` for now
|
||||||
|
|
||||||
##### Provision/Update
|
##### Provision/Update
|
||||||
|
@ -702,6 +704,7 @@ class SystemuserAppResource(AppResource):
|
||||||
default_properties: Dict[str, Any] = {
|
default_properties: Dict[str, Any] = {
|
||||||
"allow_ssh": False,
|
"allow_ssh": False,
|
||||||
"allow_sftp": False,
|
"allow_sftp": False,
|
||||||
|
"allow_email": False,
|
||||||
"home": "/var/www/__APP__",
|
"home": "/var/www/__APP__",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,9 +712,13 @@ class SystemuserAppResource(AppResource):
|
||||||
|
|
||||||
allow_ssh: bool = False
|
allow_ssh: bool = False
|
||||||
allow_sftp: bool = False
|
allow_sftp: bool = False
|
||||||
|
allow_email: bool = False
|
||||||
home: str = ""
|
home: str = ""
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
|
from yunohost.app import regen_mail_app_user_config_for_dovecot_and_postfix
|
||||||
|
|
||||||
# FIXME : validate that no yunohost user exists with that name?
|
# FIXME : validate that no yunohost user exists with that name?
|
||||||
# and/or that no system user exists during install ?
|
# and/or that no system user exists during install ?
|
||||||
|
|
||||||
|
@ -756,7 +763,25 @@ class SystemuserAppResource(AppResource):
|
||||||
f"sed -i 's@{raw_user_line_in_etc_passwd}@{new_raw_user_line_in_etc_passwd}@g' /etc/passwd"
|
f"sed -i 's@{raw_user_line_in_etc_passwd}@{new_raw_user_line_in_etc_passwd}@g' /etc/passwd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update mail-related stuff
|
||||||
|
if self.allow_email:
|
||||||
|
mail_pwd = self.get_setting("mail_pwd")
|
||||||
|
if not mail_pwd:
|
||||||
|
mail_pwd = random_ascii(24)
|
||||||
|
self.set_setting("mail_pwd", mail_pwd)
|
||||||
|
|
||||||
|
regen_mail_app_user_config_for_dovecot_and_postfix()
|
||||||
|
else:
|
||||||
|
self.delete_setting("mail_pwd")
|
||||||
|
if os.system(f"grep --quiet ' {self.app}$' /etc/postfix/app_senders_login_maps") == 0 \
|
||||||
|
or os.system(f"grep --quiet '^{self.app}:' /etc/dovecot/app-senders-passwd") == 0:
|
||||||
|
regen_mail_app_user_config_for_dovecot_and_postfix()
|
||||||
|
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
|
from yunohost.app import regen_mail_app_user_config_for_dovecot_and_postfix
|
||||||
|
|
||||||
if os.system(f"getent passwd {self.app} >/dev/null 2>/dev/null") == 0:
|
if os.system(f"getent passwd {self.app} >/dev/null 2>/dev/null") == 0:
|
||||||
os.system(f"deluser {self.app} >/dev/null")
|
os.system(f"deluser {self.app} >/dev/null")
|
||||||
if os.system(f"getent passwd {self.app} >/dev/null 2>/dev/null") == 0:
|
if os.system(f"getent passwd {self.app} >/dev/null 2>/dev/null") == 0:
|
||||||
|
@ -771,6 +796,11 @@ class SystemuserAppResource(AppResource):
|
||||||
f"Failed to delete system user for {self.app}", raw_msg=True
|
f"Failed to delete system user for {self.app}", raw_msg=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.delete_setting("mail_pwd")
|
||||||
|
if os.system(f"grep --quiet ' {self.app}$' /etc/postfix/app_senders_login_maps") == 0 \
|
||||||
|
or os.system(f"grep --quiet '^{self.app}:' /etc/dovecot/app-senders-passwd") == 0:
|
||||||
|
regen_mail_app_user_config_for_dovecot_and_postfix()
|
||||||
|
|
||||||
# FIXME : better logging and error handling, add stdout/stderr from the deluser/delgroup commands...
|
# FIXME : better logging and error handling, add stdout/stderr from the deluser/delgroup commands...
|
||||||
|
|
||||||
|
|
||||||
|
@ -1315,8 +1345,6 @@ class DatabaseAppResource(AppResource):
|
||||||
self.set_setting("db_pwd", db_pwd)
|
self.set_setting("db_pwd", db_pwd)
|
||||||
|
|
||||||
if not db_pwd:
|
if not db_pwd:
|
||||||
from moulinette.utils.text import random_ascii
|
|
||||||
|
|
||||||
db_pwd = random_ascii(24)
|
db_pwd = random_ascii(24)
|
||||||
self.set_setting("db_pwd", db_pwd)
|
self.set_setting("db_pwd", db_pwd)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue