def updateSenderAccess(self, oldMail = None, oldDomain = None): conf = FLSConfig.getInstance() mailAddr = '%s@%s' % (self.mail, self.domain) if oldMail is None: oldMail = self.mail if oldDomain is None: oldDomain = self.domain mailOldAddr = '%s@%s' % (oldMail, oldDomain) cnt = [] with open(conf.get('mailserver', 'senderaccess'), 'r') as f: cnt = f.read().split('\n') cnt = [f for f in cnt if (('\t' in f and f[0:f.index('\t')] != mailOldAddr) or f[0:1] == '#') and len(f.strip()) > 0] # now add data: if self.state in (MailAccount.STATE_CHANGE, MailAccount.STATE_CREATE): if self.enabled: cnt.append('%s\t%s' % (mailAddr, 'OK')) else: cnt.append('%s\t%s' % (mailAddr, '4508q 4.2.1 User is disabled at the moment')) # now sort file cnt.sort() # now write back try: with open(conf.get('mailserver', 'senderaccess'), 'w') as f: f.write('\n'.join(cnt)) except: return False else: # postmap return hashPostFile(conf.get('mailserver', 'senderaccess'), conf.get('mailserver', 'postmap'))
def updateMailboxes(self, oldMail = None, oldDomain = None): conf = FLSConfig.getInstance() mailAddr = '%s@%s' % (self.mail, self.domain) if oldMail is None: oldMail = self.mail if oldDomain is None: oldDomain = self.domain mailOldAddr = '%s@%s' % (oldMail, oldDomain) cnt = [] with open(conf.get('mailserver', 'mailboxes'), 'r') as f: cnt = f.read().split('\n') cnt = [f for f in cnt if (('\t' in f and f[0:f.index('\t')] != mailOldAddr) or f[0:1] == '#') and len(f.strip()) > 0] # now add data: if self.state in (MailAccount.STATE_CHANGE, MailAccount.STATE_CREATE): if self.type == MailAccount.TYPE_ACCOUNT: cnt.append('%s\t%s%s%s%s' % (mailAddr, self.domain, os.sep, self.mail, os.sep)) # now sort file cnt.sort() # now write back try: with open(conf.get('mailserver', 'mailboxes'), 'w') as f: f.write('\n'.join(cnt)) except: return False else: # postmap return hashPostFile(conf.get('mailserver', 'mailboxes'), conf.get('mailserver', 'postmap'))
def delete(self): log = logging.getLogger('flscp') conf = FLSConfig.getInstance() # delete! # 1. remove credentials # 2. remove entry from /etc/postfix/fls/aliases # 3. remove entry from /etc/postfix/fls/mailboxes # 4. remove entry from /etc/postfix/fls/sender-access # 5. remove entry from mail_users # 7. remove complete mails in /var/mail/,... directory # 6. postmap all relevant entries self.updateCredentials() self.updateMailboxes() self.updateAliases() self.updateSenderAccess() if self.exists(): db = MailDatabase.getInstance() cx = db.getCursor() query = ('SELECT mail_id, mail_addr FROM mail_users WHERE mail_id = %s') cx.execute(query, (self.id,)) for (mail_id, mail_addr,) in cx: (mail, domain) = mail_addr.split('@') path = '%s/%s/%s/' % (conf.get('mailserver', 'basemailpath'), domain, mail) if os.path.exists(path): try: os.removedirs(path) except Exception as e: log.warning('Error when removing directory: %s' % (e,)) query = ('DELETE FROM mail_users WHERE mail_id = %s') cx.execute(query, (self.id,)) cx.close()
def updateCredentials(self): conf = FLSConfig.getInstance() if not conf.getboolean('features', 'sasldb'): return None db = SaslDatabase.getInstance() if self.state == MailAccount.STATE_DELETE or len(self.hashPw.strip()) <= 0: db.delete(self.credentialsKey()) else: if db.exists(self.credentialsKey()): db.update(self.credentialsKey(), self.pw) else: db.add(self.credentialsKey(), self.pw)
def recalculateQuota(self): log = logging.getLogger('flscp') conf = FLSConfig.getInstance() cmd = shlex.split('%s quota recalc -u %s' % (conf.get('mailserver', 'doveadm'), '%s@%s' % (self.mail, self.domain))) state = True with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: out = p.stdout.read() err = p.stderr.read() if len(out) > 0: log.info(out) if len(err) > 0: log.warning(err) state = False self.setState(MailAccount.STATE_OK) return state
def updateAliases(self, oldMail = None, oldDomain = None): conf = FLSConfig.getInstance() mailAddr = '%s@%s' % (self.mail, self.domain) if oldMail is None: oldMail = self.mail if oldDomain is None: oldDomain = self.domain mailOldAddr = '%s@%s' % (oldMail, oldDomain) cnt = [] with open(conf.get('mailserver', 'aliases'), 'r') as f: cnt = f.read().split('\n') cnt = [f for f in cnt if (('\t' in f and f[0:f.index('\t')] != mailOldAddr) or f[0:1] == '#') and len(f.strip()) > 0] # now add data: if self.state in (MailAccount.STATE_CHANGE, MailAccount.STATE_CREATE): forward = copy.copy(self.forward) # remove all empty things i = 0 for f in forward: if len(f.strip()) <= 0: del(forward[i]) i += 1 if self.type == MailAccount.TYPE_ACCOUNT: forward.insert(0, mailAddr) forward = list(set(forward)) cnt.append('%s\t%s' % (mailAddr, ','.join(forward))) # now sort file cnt.sort() # now write back try: with open(conf.get('mailserver', 'aliases'), 'w') as f: f.write('\n'.join(cnt)) except: return False else: # postmap return hashPostFile(conf.get('mailserver', 'aliases'), conf.get('mailserver', 'postmap'))
def authenticate(self, mech, pwd, cert = None): conf = FLSConfig.getInstance() log = logging.getLogger('flscp') data = { 'userdb_user': '', 'userdb_home': '', 'userdb_uid': '', 'userdb_gid': '', 'userdb_mail': '', 'quota_rule': '', 'nopassword': 1 } localPartDir = os.path.join(conf.get('mailserver', 'basemailpath'), 'virtual') homeDir = os.path.join(localPartDir, self.domain, self.mail) username = ('%s@%s' % (self.mail, self.domain)).lower() if self.hashPw == '_no_': log.debug('User %s can not login, because password is disabled!' % (self.getMailAddress(),)) return False s = SaltEncryption() if mech in ['PLAIN', 'LOGIN']: state = s.compare(pwd, self.hashPw) elif mech in ['EXTERNAL']: state = (cert.lower() == 'valid' and pwd == '') else: log.debug('User %s can not login: unsupported auth mechanism "%s"' % (self.getMailAddress(), mech)) state = False if state: data['userdb_user'] = username data['userdb_home'] = self.getHomeDir() data['userdb_uid'] = conf.get('mailserver', 'uid') data['userdb_gid'] = conf.get('mailserver', 'gid') data['userdb_mail'] = self.getMailDirFormat() data['quota_rule'] = '*:storage=%sb' % (self.quota,) return data else: return False
def __init__(self): conf = FLSConfig.getInstance() self.id = None self.type = MailAccount.TYPE_ACCOUNT self.state = MailAccount.STATE_OK if conf is not None: self.quota = conf.getint('userdefault', 'quota') else: # 100 MB self.quota = 104857600 self.quotaSts = 0.0 self.mail = '' self.domain = '' self.pw = '' self.hashPw = '' self.genPw = False self.altMail = '' self.forward = [] self.authCode = None self.authValid = None self.enabled = True
def save(self): log = logging.getLogger('flscp') conf = FLSConfig.getInstance() if self.state == MailAccount.STATE_CREATE: self.create() return elif self.state == MailAccount.STATE_DELETE: self.delete() return elif self.state == MailAccount.STATE_QUOTA: self.recalculateQuota() return # now save! # -> see create - but if key changed (mail address!) remove # all entries before and rename folder in /var/mail,... directory # get original data! if not self.exists(): self.create() # get domain id! (if not exist: create!) try: d = Domain.getByName(self.domain) except KeyError: raise # pw entered? if len(self.pw.strip()) > 0: log.info('Hash password for user %s' % (self.mail,)) self.hashPassword() db = MailDatabase.getInstance() cx = db.getCursor() query = ('SELECT mail_id, mail_addr, mail_type FROM mail_users WHERE mail_id = %s') cx.execute(query, (self.id,)) (mail_id, mail_addr, mail_type) = cx.fetchone() (mail, domain) = mail_addr.split('@') cx.close() cx = db.getCursor() if (self.type == MailAccount.TYPE_ACCOUNT and self.hashPw != '') \ or (self.type == MailAccount.TYPE_FWDSMTP and self.hashPw != '') \ or self.type == MailAccount.TYPE_FORWARD: query = ( 'UPDATE mail_users SET mail_acc = %s, mail_pass = %s, mail_forward = %s, ' \ 'domain_id = %s, mail_type = %s, status = %s, quota = %s, mail_addr = %s, ' \ 'alternative_addr = %s, enabled = %s WHERE mail_id = %s' ) params = ( self.mail, self.hashPw, ','.join(self.forward), d.id, self.type, self.state, self.quota, '%s@%s' % (self.mail, self.domain), self.altMail, str(int(self.enabled)), self.id ) else: query = ( 'UPDATE mail_users SET mail_acc = %s, mail_forward = %s, ' \ 'domain_id = %s, mail_type = %s, status = %s, quota = %s, mail_addr = %s, ' \ 'alternative_addr = %s, enabled = %s WHERE mail_id = %s' ) params = ( self.mail, ','.join(self.forward), d.id, self.type, self.state, self.quota, '%s@%s' % (self.mail, self.domain), self.altMail, str(int(self.enabled)), self.id ) cx.execute( query, params ) db.commit() log.debug('executed mysql statement: %s' % (cx.statement,)) # update credentials... # if pw was entered or type changed if mail_type != self.type or self.pw.strip() != '': self.updateCredentials() # now update mailboxes files! if not self.updateMailboxes(oldMail=mail, oldDomain=domain): cx.close() return False # update aliases if not self.updateAliases(oldMail=mail, oldDomain=domain): # remove entry from updateMailboxes? cx.close() return False # update sender-access if not self.updateSenderAccess(oldMail=mail, oldDomain=domain): # remove entry from updateMailboxes and Aliases ? cx.close() return False # rename folders - but only if target directory does not exist # (we had to throw fatal error if target directory exists!) oldPath = '%s/%s/%s/' % (conf.get('mailserver', 'basemailpath'), domain, mail) path = '%s/%s/%s/' % (conf.get('mailserver', 'basemailpath'), self.domain, self.mail) if os.path.exists(oldPath): if os.path.exists(path): log.error('Could not move "%s" to "%s", because it already exists!' % (path,)) else: try: os.rename(oldPath, path) except OSError as e: log.warning('Got OSError - Does directory exists? (%s)' % (e,)) except Exception as e: log.warning('Got unexpected exception (%s)!' % (e,)) cx.close() # all best? Than go forward and update set state,... self.setState(MailAccount.STATE_OK) # notify if len(self.altMail) > 0: m = Mailer(self) state = False if self.type == MailAccount.TYPE_ACCOUNT \ or self.type == MailAccount.TYPE_FWDSMTP: state = m.changeAccount() else: state = m.changeForward() if state: log.info('User is notified about account change!') else: log.warning('Unknown error while notifying user!') else: log.info('User is not notified because we have no address of him!') # reset info self.pw = '' self.hashPw = '' self.genPw = False
def getHomeDir(self): conf = FLSConfig.getInstance() return os.path.join(conf.get('mailserver', 'basemailpath'), 'virtual', self.domain, self.mail)
__author__ = 'Lukas Schreiner' __copyright__ = 'Copyright (C) 2013 - 2015 Website-Team Friedrich-List-Schule-Wiesbaden' __version__ = '0.8' FORMAT = '%(asctime)-15s %(message)s' formatter = logging.Formatter(FORMAT, datefmt='%b %d %H:%M:%S') log = logging.getLogger('flscp') log.setLevel(logging.INFO) hdlr = ColorizingStreamHandler() hdlr.setFormatter(formatter) log.addHandler(hdlr) workDir = os.path.dirname(os.path.realpath(__file__)) # search for config conf = FLSConfig() fread = conf.read( [ 'server.ini', os.path.expanduser('~/.flscpserver.ini'), os.path.expanduser('~/.flscp/server.ini'), os.path.expanduser('~/.config/flscp/server.ini'), '/etc/flscp/server.ini', '/usr/local/etc/flscp/server.ini' ] ) if len(fread) <= 0: sys.stderr.write( 'Missing config file in one of server.ini, ~/.flscpserver.ini, ~/.flscp/server.ini, ~/.config/flscp/server.ini, \ /etc/flscp/server.ini or /usr/local/etc/flscp/server.ini!\n' ) sys.exit(255) else: log.debug('Using config files "%s"' % (fread.pop(),))
def __init__(self): super().__init__() SaslDatabase.__instance = self self.conf = FLSConfig.getInstance() self.log = logging.getLogger('flscp')