2014-10-09 17:04:12 +00:00
|
|
|
import logging
|
2014-09-30 16:06:42 +00:00
|
|
|
import textwrap
|
2014-08-22 15:31:44 +00:00
|
|
|
import os
|
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2014-08-22 15:31:44 +00:00
|
|
|
from django.utils import timezone
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
|
|
|
|
from orchestra.apps.orchestration import ServiceController
|
|
|
|
from orchestra.apps.resources import ServiceMonitor
|
|
|
|
|
|
|
|
from . import settings
|
2014-09-26 15:05:20 +00:00
|
|
|
from .models import Address
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
# TODO http://wiki2.dovecot.org/HowTo/SimpleVirtualInstall
|
|
|
|
# TODO http://wiki2.dovecot.org/HowTo/VirtualUserFlatFilesPostfix
|
|
|
|
# TODO mount the filesystem with "nosuid" option
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
class PasswdVirtualUserBackend(ServiceController):
|
|
|
|
verbose_name = _("Mail virtual user (passwd-file)")
|
2014-10-17 10:04:47 +00:00
|
|
|
model = 'mailboxes.Mailbox'
|
2014-09-30 14:46:29 +00:00
|
|
|
# TODO related_models = ('resources__content_type') ?? needed for updating disk usage from resource.data
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
DEFAULT_GROUP = 'postfix'
|
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
def set_user(self, context):
|
|
|
|
self.append(textwrap.dedent("""
|
|
|
|
if [[ $( grep "^%(username)s:" %(passwd_path)s ) ]]; then
|
2014-10-07 13:08:59 +00:00
|
|
|
sed -i 's#^%(username)s:.*#%(passwd)s#' %(passwd_path)s
|
2014-09-30 16:06:42 +00:00
|
|
|
else
|
2014-10-06 14:57:02 +00:00
|
|
|
echo '%(passwd)s' >> %(passwd_path)s
|
2014-09-30 16:06:42 +00:00
|
|
|
fi""" % context
|
|
|
|
))
|
2014-08-22 15:31:44 +00:00
|
|
|
self.append("mkdir -p %(home)s" % context)
|
2014-10-06 14:57:02 +00:00
|
|
|
self.append("chown %(uid)s.%(gid)s %(home)s" % context)
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def set_mailbox(self, context):
|
|
|
|
self.append(textwrap.dedent("""
|
|
|
|
if [[ ! $(grep "^%(username)s@%(mailbox_domain)s\s" %(virtual_mailbox_maps)s) ]]; then
|
|
|
|
echo "%(username)s@%(mailbox_domain)s\tOK" >> %(virtual_mailbox_maps)s
|
|
|
|
UPDATED_VIRTUAL_MAILBOX_MAPS=1
|
|
|
|
fi""" % context))
|
|
|
|
|
2014-08-22 15:31:44 +00:00
|
|
|
def generate_filter(self, mailbox, context):
|
2014-10-09 17:04:12 +00:00
|
|
|
self.append("doveadm mailbox create -u %(username)s Spam" % context) # TODO override webmail filters???
|
|
|
|
context['filtering_path'] = os.path.join(context['home'], '.dovecot.sieve')
|
|
|
|
filtering = mailbox.get_filtering()
|
|
|
|
if filtering:
|
|
|
|
context['filtering'] = '# %(banner)s\n' + filtering
|
|
|
|
self.append("echo '%(filtering)s' > %(filtering_path)s" % context)
|
2014-08-22 15:31:44 +00:00
|
|
|
else:
|
2014-10-09 17:04:12 +00:00
|
|
|
self.append("rm -f %(filtering_path)s" % context)
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
def save(self, mailbox):
|
|
|
|
context = self.get_context(mailbox)
|
2014-10-06 14:57:02 +00:00
|
|
|
self.set_user(context)
|
2014-10-09 17:04:12 +00:00
|
|
|
self.set_mailbox(context)
|
2014-08-22 15:31:44 +00:00
|
|
|
self.generate_filter(mailbox, context)
|
|
|
|
|
|
|
|
def delete(self, mailbox):
|
|
|
|
context = self.get_context(mailbox)
|
2014-10-06 14:57:02 +00:00
|
|
|
self.append("{ sleep 2 && killall -u %(uid)s -s KILL; } &" % context)
|
2014-10-07 13:08:59 +00:00
|
|
|
self.append("killall -u %(uid)s || true" % context)
|
2014-10-06 14:57:02 +00:00
|
|
|
self.append("sed -i '/^%(username)s:.*/d' %(passwd_path)s" % context)
|
2014-10-09 17:04:12 +00:00
|
|
|
self.append("sed -i '/^%(username)s@%(mailbox_domain)s\s.*/d' %(virtual_mailbox_maps)s" % context)
|
|
|
|
self.append("UPDATED_VIRTUAL_MAILBOX_MAPS=1")
|
2014-10-07 13:08:59 +00:00
|
|
|
# TODO delete
|
|
|
|
context['deleted'] = context['home'].rstrip('/') + '.deleted'
|
|
|
|
self.append("mv %(home)s %(deleted)s" % context)
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
def get_extra_fields(self, mailbox, context):
|
|
|
|
context['quota'] = self.get_quota(mailbox)
|
|
|
|
return 'userdb_mail=maildir:~/Maildir {quota}'.format(**context)
|
|
|
|
|
|
|
|
def get_quota(self, mailbox):
|
|
|
|
try:
|
|
|
|
quota = mailbox.resources.disk.allocated
|
|
|
|
except (AttributeError, ObjectDoesNotExist):
|
|
|
|
return ''
|
|
|
|
unit = mailbox.resources.disk.unit[0].upper()
|
|
|
|
return 'userdb_quota_rule=*:bytes=%i%s' % (quota, unit)
|
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def commit(self):
|
|
|
|
context = {
|
2014-10-17 10:04:47 +00:00
|
|
|
'virtual_mailbox_maps': settings.MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH
|
2014-10-09 17:04:12 +00:00
|
|
|
}
|
|
|
|
self.append(
|
|
|
|
"[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && { postmap %(virtual_mailbox_maps)s; }"
|
|
|
|
% context
|
|
|
|
)
|
|
|
|
|
2014-08-22 15:31:44 +00:00
|
|
|
def get_context(self, mailbox):
|
|
|
|
context = {
|
2014-09-30 14:46:29 +00:00
|
|
|
'name': mailbox.name,
|
2014-09-29 12:22:45 +00:00
|
|
|
'username': mailbox.name,
|
2014-10-06 14:57:02 +00:00
|
|
|
'password': mailbox.password if mailbox.active else '*%s' % mailbox.password,
|
|
|
|
'uid': 10000 + mailbox.pk,
|
|
|
|
'gid': 10000 + mailbox.pk,
|
|
|
|
'group': self.DEFAULT_GROUP,
|
|
|
|
'quota': self.get_quota(mailbox),
|
2014-10-17 10:04:47 +00:00
|
|
|
'passwd_path': settings.MAILBOXES_PASSWD_PATH,
|
2014-10-06 14:57:02 +00:00
|
|
|
'home': mailbox.get_home(),
|
2014-10-09 17:04:12 +00:00
|
|
|
'banner': self.get_banner(),
|
2014-10-17 10:04:47 +00:00
|
|
|
'virtual_mailbox_maps': settings.MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH,
|
|
|
|
'mailbox_domain': settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN,
|
2014-08-22 15:31:44 +00:00
|
|
|
}
|
2014-10-06 14:57:02 +00:00
|
|
|
context['extra_fields'] = self.get_extra_fields(mailbox, context)
|
2014-10-09 17:04:12 +00:00
|
|
|
context['passwd'] = '{username}:{password}:{uid}:{gid}::{home}::{extra_fields}'.format(**context)
|
2014-08-22 15:31:44 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class PostfixAddressBackend(ServiceController):
|
|
|
|
verbose_name = _("Postfix address")
|
2014-10-17 10:04:47 +00:00
|
|
|
model = 'mailboxes.Address'
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def include_virtual_alias_domain(self, context):
|
|
|
|
self.append(textwrap.dedent("""
|
|
|
|
[[ $(grep "^\s*%(domain)s\s*$" %(virtual_alias_domains)s) ]] || {
|
|
|
|
echo "%(domain)s" >> %(virtual_alias_domains)s
|
|
|
|
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
|
|
|
|
}""" % context
|
|
|
|
))
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def exclude_virtual_alias_domain(self, context):
|
2014-08-22 15:31:44 +00:00
|
|
|
domain = context['domain']
|
|
|
|
if not Address.objects.filter(domain=domain).exists():
|
2014-10-09 17:04:12 +00:00
|
|
|
self.append('sed -i "/^%(domain)s\s*/d" %(virtual_alias_domains)s' % context)
|
|
|
|
|
|
|
|
def update_virtual_alias_maps(self, address, context):
|
|
|
|
destination = []
|
|
|
|
for mailbox in address.get_mailboxes():
|
|
|
|
context['mailbox'] = mailbox
|
|
|
|
destination.append("%(mailbox)s@%(mailbox_domain)s" % context)
|
|
|
|
for forward in address.forward:
|
|
|
|
if '@' in forward:
|
|
|
|
destination.append(forward)
|
|
|
|
if destination:
|
|
|
|
context['destination'] = ' '.join(destination)
|
|
|
|
self.append(textwrap.dedent("""
|
|
|
|
LINE="%(email)s\t%(destination)s"
|
|
|
|
if [[ ! $(grep "^%(email)s\s" %(virtual_alias_maps)s) ]]; then
|
|
|
|
echo "${LINE}" >> %(virtual_alias_maps)s
|
|
|
|
UPDATED_VIRTUAL_ALIAS_MAPS=1
|
|
|
|
else
|
|
|
|
if [[ ! $(grep "^${LINE}$" %(virtual_alias_maps)s) ]]; then
|
|
|
|
sed -i "s/^%(email)s\s.*$/${LINE}/" %(virtual_alias_maps)s
|
|
|
|
UPDATED_VIRTUAL_ALIAS_MAPS=1
|
|
|
|
fi
|
|
|
|
fi""" % context
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
logger.warning("Address %i is empty" % address.pk)
|
|
|
|
self.append('sed -i "/^%(email)s\s/d" %(virtual_alias_maps)s')
|
|
|
|
self.append('UPDATED_VIRTUAL_ALIAS_MAPS=1')
|
2014-08-22 15:31:44 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def exclude_virtual_alias_maps(self, context):
|
2014-10-06 14:57:02 +00:00
|
|
|
self.append(textwrap.dedent("""
|
2014-09-30 16:06:42 +00:00
|
|
|
if [[ $(grep "^%(email)s\s") ]]; then
|
2014-10-09 17:04:12 +00:00
|
|
|
sed -i "/^%(email)s\s.*$/d" %(virtual_alias_maps)s
|
|
|
|
UPDATED_VIRTUAL_ALIAS_MAPS=1
|
2014-09-30 16:06:42 +00:00
|
|
|
fi"""
|
|
|
|
))
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
def save(self, address):
|
|
|
|
context = self.get_context(address)
|
2014-10-09 17:04:12 +00:00
|
|
|
self.include_virtual_alias_domain(context)
|
|
|
|
self.update_virtual_alias_maps(address, context)
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
def delete(self, address):
|
|
|
|
context = self.get_context(address)
|
2014-10-09 17:04:12 +00:00
|
|
|
self.exclude_virtual_alias_domain(context)
|
|
|
|
self.exclude_virtual_alias_maps(context)
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
def commit(self):
|
|
|
|
context = self.get_context_files()
|
2014-10-06 14:57:02 +00:00
|
|
|
self.append(textwrap.dedent("""
|
2014-10-09 17:04:12 +00:00
|
|
|
[[ $UPDATED_VIRTUAL_ALIAS_MAPS == 1 ]] && { postmap %(virtual_alias_maps)s; }
|
|
|
|
[[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]] && { /etc/init.d/postfix reload; }
|
2014-10-06 14:57:02 +00:00
|
|
|
""" % context
|
2014-09-30 16:06:42 +00:00
|
|
|
))
|
2014-08-22 15:31:44 +00:00
|
|
|
|
|
|
|
def get_context_files(self):
|
|
|
|
return {
|
2014-10-17 10:04:47 +00:00
|
|
|
'virtual_alias_domains': settings.MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH,
|
|
|
|
'virtual_alias_maps': settings.MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH
|
2014-08-22 15:31:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def get_context(self, address):
|
|
|
|
context = self.get_context_files()
|
|
|
|
context.update({
|
|
|
|
'domain': address.domain,
|
|
|
|
'email': address.email,
|
2014-10-17 10:04:47 +00:00
|
|
|
'mailbox_domain': settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN,
|
2014-08-22 15:31:44 +00:00
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class AutoresponseBackend(ServiceController):
|
|
|
|
verbose_name = _("Mail autoresponse")
|
|
|
|
model = 'mail.Autoresponse'
|
|
|
|
|
|
|
|
|
|
|
|
class MaildirDisk(ServiceMonitor):
|
2014-10-17 10:04:47 +00:00
|
|
|
model = 'mailboxes.Mailbox'
|
2014-08-22 15:31:44 +00:00
|
|
|
resource = ServiceMonitor.DISK
|
|
|
|
verbose_name = _("Maildir disk usage")
|
|
|
|
|
|
|
|
def monitor(self, mailbox):
|
|
|
|
context = self.get_context(mailbox)
|
|
|
|
self.append(
|
|
|
|
"SIZE=$(sed -n '2p' %(maildir_path)s | cut -d' ' -f1)\n"
|
|
|
|
"echo %(object_id)s ${SIZE:-0}" % context
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_context(self, mailbox):
|
2014-10-23 21:25:44 +00:00
|
|
|
home = mailbox.get_home()
|
|
|
|
context = {
|
|
|
|
'maildir_path': os.path.join(home, 'Maildir/maildirsize'),
|
2014-10-06 14:57:02 +00:00
|
|
|
'object_id': mailbox.pk
|
2014-10-23 21:25:44 +00:00
|
|
|
}
|
2014-08-22 15:31:44 +00:00
|
|
|
return context
|