def get_header(self, msg, header): # msg parameter to maintain compatibility with # modoboa_webmail.lib.imapemail.ImapEmail if header in msg: decoded_values = [] for value, encoding in email.header.decode_header(msg[header]): if not len(value): continue if encoding: value = smart_text(value, encoding=encoding) elif isinstance(value, six.binary_type): # SMTPUTF8 fallback (most of the time) # Address contains non ASCII chars but is not RFC2047 # encoded... encoding = chardet.detect(value) try: value = value.decode(encoding["encoding"], "replace") except (TypeError, UnicodeDecodeError) as exc: six.raise_from( InternalError( _("unable to determine encoding of string") ), exc ) decoded_values.append(value) return "".join(decoded_values) return ""
def mail_home(self): """Retrieve the home directory of this mailbox. The home directory refers to the place on the file system where the mailbox data is stored. We ask dovecot to give us this information because there are several patterns to understand and we don't want to implement them. """ hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) if hm is None or hm == "no": return None if self.__mail_home is None: curuser = pwd.getpwuid(os.getuid()).pw_name mbowner = parameters.get_admin("MAILBOXES_OWNER") options = {} if curuser != mbowner: options['sudo_user'] = mbowner code, output = exec_cmd( "doveadm user %s -f home" % self.full_address, **options) if code: raise InternalError( _("Failed to retrieve mailbox location (%s)" % output)) self.__mail_home = output.strip() return self.__mail_home
def _get_domain_from_rcpt(self, rcpt): """Retrieve a domain from a recipient address.""" local_part, domname = split_mailbox(rcpt) domain = admin_models.Domain.objects.filter(name=domname).first() if not domain: raise InternalError(_("Local domain not found")) return domain
def hdelimiter(self): """Return the default hierachy delimiter. :return: a string """ if self.__hdelimiter is None: raise InternalError(_("Failed to retrieve hierarchy delimiter")) return self.__hdelimiter
def init_storage_dir(): """Create the directory whare documents will be stored.""" storage_dir = parameters.get_admin("STORAGE_DIR") if os.path.exists(storage_dir): return try: os.mkdir(storage_dir) except (OSError, IOError) as inst: raise InternalError( _("Failed to create the directory that will contain " "PDF documents (%s)") % inst)
def _find_binary(self, name): """Find path to binary.""" code, output = exec_cmd("which {}".format(name)) if not code: return smart_text(output).strip() known_paths = getattr(settings, "SA_LOOKUP_PATH", ("/usr/bin", )) for path in known_paths: bpath = os.path.join(path, name) if os.path.isfile(bpath) and os.access(bpath, os.X_OK): return bpath raise InternalError(_("Failed to find {} binary").format(name))
def delete_ldap_account(user, config): """Delete remote LDAP account.""" dn = config["ldap_sync_account_dn_template"] % {"user": user.username} conn = get_connection(config) if not check_if_dn_exists(conn, dn): return if config["ldap_sync_delete_remote_account"]: try: conn.delete_s(dn) except ldap.LDAPError as e: raise InternalError( _("Failed to delete LDAP account: {}").format(e)) else: password = get_user_password(user, True) ldif = [(ldap.MOD_REPLACE, "userPassword", password)] try: conn.modify_s(dn, ldif) except ldap.LDAPError as e: raise InternalError( _("Failed to disable LDAP account: {}").format(e))
def update_user_password(self, user, password, newpassword): try: self.connect_to_server(user, password) if self.ldap_ad: newpassword = (( '"%s"' % newpassword).encode('utf-16').lstrip('\377\376')) ldif = [(ldap.MOD_REPLACE, self.pwd_attr, self._crypt_password(newpassword))] self.conn.modify_s(self.user_dn, ldif) except ldap.LDAPError as e: raise InternalError(_("Failed to update password: %s" % str(e)))
def __init__(self): conf = dict(param_tools.get_global_parameters("modoboa_amavis")) try: if conf["am_pdp_mode"] == "inet": self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((conf["am_pdp_host"], conf["am_pdp_port"])) else: self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(conf["am_pdp_socket"]) except socket.error as err: raise InternalError(_("Connection to amavis failed: %s" % str(err)))
def update_user_password(self, user, password, newpassword): """Update user password.""" self.connect_to_server(user, password) user_dn = self._find_user_dn(user) if self.ldap_ad: newpassword = (('"%s"' % newpassword).encode('utf-16').lstrip('\377\376')) ldif = [(ldap.MOD_REPLACE, self.pwd_attr, self._crypt_password(newpassword))] try: self.conn.modify_s(user_dn, ldif) except ldap.LDAPError as e: raise InternalError(_("Failed to update password: {}").format(e))
def hdelimiter(self): """Return the default hierachy delimiter. This is a simple way to retrieve the default delimiter (see http://www.imapwiki.org/ClientImplementation/MailboxList). :return: a string """ if self.__hdelimiter is None: data = self._cmd("LIST", "", "") m = self.list_response_pattern.match(data[0]) if m is None: raise InternalError(_("Failed to retrieve hierarchy delimiter")) self.__hdelimiter = m.group('delimiter') return self.__hdelimiter
def db_type(cname="default"): """Return the type of the *default* database Supported values : 'postgres', 'mysql', 'sqlite' :param str cname: connection name :return: a string or None """ if cname not in settings.DATABASES: raise InternalError( _("Connection to database %s not configured" % cname)) for t in ["postgres", "mysql", "sqlite"]: if settings.DATABASES[cname]["ENGINE"].find(t) != -1: return t return None
def __init__(self): mode = parameters.get_admin("AM_PDP_MODE") try: if mode == "inet": host = parameters.get_admin('AM_PDP_HOST') port = parameters.get_admin('AM_PDP_PORT') self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, int(port))) else: path = parameters.get_admin('AM_PDP_SOCKET') self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(path) except socket.error, err: raise InternalError(_("Connection to amavis failed: %s" % str(err)))
def _get_mailbox_from_rcpt(self, rcpt): """Retrieve a mailbox from a recipient address.""" local_part, domname = split_mailbox(rcpt) try: mailbox = Mailbox.objects.select_related("domain").get( address=local_part, domain__name=domname) except Mailbox.DoesNotExist: try: alias = Alias.objects.select_related("domain").get( address=local_part, domain__name=domname) except Alias.DoesNotExist: raise InternalError(_("No recipient found")) if alias.type != "alias": return None mailbox = alias.mboxes.all()[0] return mailbox
def decode(value_bytes, encoding, append_to_error=""): """Try to decode the given string.""" assert isinstance(value_bytes, bytes),\ "value_bytes should be of type bytes" if len(value_bytes) == 0: # short circuit for empty strings return "" try: value = value_bytes.decode(encoding) except (UnicodeDecodeError, LookupError): encoding = chardet.detect(value_bytes) try: value = value_bytes.decode(encoding["encoding"], "replace") except (TypeError, UnicodeDecodeError) as exc: raise InternalError( _("unable to determine encoding of string") + append_to_error) from exc return value
def _get_mailbox_from_rcpt(self, rcpt): """Retrieve a mailbox from a recipient address.""" local_part, domname, extension = (split_mailbox(rcpt, return_extension=True)) try: mailbox = admin_models.Mailbox.objects.select_related( "domain").get(address=local_part, domain__name=domname) except admin_models.Mailbox.DoesNotExist: alias = admin_models.Alias.objects.filter( address="{}@{}".format(local_part, domname), aliasrecipient__r_mailbox__isnull=False).first() if not alias: raise InternalError(_("No recipient found")) if alias.type != "alias": return None mailbox = alias.aliasrecipient_set.filter( r_mailbox__isnull=False).first() return mailbox
def set_password(self, raw_value, curvalue=None): """Password update Update the current mailbox's password with the given clear value. This value is encrypted according to the defined method before it is saved. :param raw_value: the new password's value :param curvalue: the current password (for LDAP authentication) """ if self.is_local: self.password = self._crypt_password(raw_value) else: if not ldap_available: raise InternalError( _("Failed to update password: LDAP module not installed")) LDAPAuthBackend().update_user_password(self.username, curvalue, raw_value) events.raiseEvent("PasswordUpdated", self, raw_value, self.pk is None)
def decode(value_bytes, encoding, append_to_error=""): """Try to decode the given string.""" assert isinstance(value_bytes, six.binary_type),\ "value_bytes should be of type %s" % six.binary_type.__name__ if len(value_bytes) == 0: # short circuit for empty strings return "" try: value = value_bytes.decode(encoding) except UnicodeDecodeError: encoding = chardet.detect(value_bytes) try: value = value_bytes.decode(encoding["encoding"], "replace") except (TypeError, UnicodeDecodeError) as exc: six.raise_from( InternalError( _("unable to determine encoding of string") + append_to_error), exc) return value
def update_ldap_account(user, config): """Update existing account.""" dn = config["ldap_sync_account_dn_template"] % {"user": user.username} conn = get_connection(config) if not check_if_dn_exists(conn, dn): create_ldap_account(user, dn, conn) return password = get_user_password(user, not user.is_active) ldif = [(ldap.MOD_REPLACE, "uid", force_bytes(user.username)), (ldap.MOD_REPLACE, "sn", force_bytes(user.last_name)), (ldap.MOD_REPLACE, "givenName", force_bytes(user.first_name)), (ldap.MOD_REPLACE, "cn", force_bytes(user.username)), (ldap.MOD_REPLACE, "displayName", force_bytes(user.fullname)), (ldap.MOD_REPLACE, "mail", force_bytes(user.email)), (ldap.MOD_REPLACE, "homePhone", force_bytes(user.phone_number)), (ldap.MOD_REPLACE, "userPassword", password)] try: conn.modify_s(dn, ldif) except ldap.LDAPError as e: raise InternalError(_("Failed to update LDAP account: {}").format(e))
def save_attachment(f): """Save a new attachment to the filesystem. The attachment is not saved using its own name to the filesystem. To avoid conflicts, a random name is generated and used instead. :param f: an uploaded file object (see Django's documentation) :return: the new random name """ from tempfile import NamedTemporaryFile dstdir = os.path.join(settings.MEDIA_ROOT, "webmail") try: fp = NamedTemporaryFile(dir=dstdir, delete=False) except Exception as e: raise InternalError(str(e)) for chunk in f.chunks(): fp.write(chunk) fp.close() return fp.name
def create_ldap_account(user, dn, conn): """Create new account.""" scheme, password = user.password.split("}") attrs = { "objectClass": [force_bytes("inetOrgPerson"), force_bytes("organizationalPerson")], "uid": [force_bytes(user.username)], "sn": [force_bytes(user.last_name)], "givenName": [force_bytes(user.first_name)], "cn": [force_bytes(user.username)], "displayName": [force_bytes(user.fullname)], "mail": [force_bytes(user.email), force_bytes(user.secondary_email)], "homePhone": [force_bytes(user.phone_number)], "userPassword": [get_user_password(user)] } ldif = modlist.addModlist(attrs) try: conn.add_s(dn, ldif) except ldap.LDAPError as e: raise InternalError(_("Failed to create LDAP account: {}").format(e))
def set_password(self, raw_value, curvalue=None): """Password update Update the current mailbox's password with the given clear value. This value is encrypted according to the defined method before it is saved. :param raw_value: the new password's value :param curvalue: the current password (for LDAP authentication) """ ldap_sync_enable = param_tools.get_global_parameter("ldap_enable_sync") if self.is_local or ldap_sync_enable: self.password = self._crypt_password(raw_value) else: if not ldap_available: raise InternalError( _("Failed to update password: LDAP module not installed")) LDAPAuthBackend().update_user_password(self.username, curvalue, raw_value) signals.account_password_updated.send(sender=self.__class__, account=self, password=raw_value, created=self.pk is None)