#!/usr/bin/env python # encoding: utf-8 """ EmailAddressAdmin.py Provides various functions for email addresses created using my Postfix Virtual hosting method. Make sure to change the UID and GID for the vmail user! Created by Joe Topjian on 2006-07-30. """ import sys import os import subprocess import datetime import os.path import shutil class EmailAddressAdmin (object): vmailUID = 1001 vmailGID = 1001 def __init__(self, address): self.address = address (self.name, self.domain) = address.split('@') if not os.path.isfile('/etc/postfix/virtual/domains/%s' % self.domain): raise "Domain does not exist." def _createPassword(self, password): """ Simple function that makes a system call to the dovecotpw command. Returns the hashed password. """ passwordCommand = "/usr/sbin/dovecotpw -p %s" % password encPassword = subprocess.Popen(passwordCommand.split(), stdout=subprocess.PIPE).communicate()[0] return encPassword.rstrip() def _backup(self, f): """ Creates a backup copy of a given file in the same directory with the date and time appended to it. Returns True if successful """ time = datetime.datetime.now() date = "%s-%s" % (time.date(), str(time.time()).replace(':','').split('.')[0]) (directory, filename) = os.path.split(f) try: shutil.copyfile(f, "/etc/postfix/virtual/backup/%s.%s" % (filename, date)) return True except Exception, e: print "Could not copy file: %s" % e def _compileMaps(self): """ Takes all the password maps in the domain files and combines them into one master file. Returns True """ try: maps = [] for domain in os.listdir('/etc/postfix/virtual/domains'): f = open("/etc/postfix/virtual/domains/%s" % domain) for line in f: maps.append(line) f.close() # make a backup of the file first self._backup('/etc/postfix/virtual/vmailboxes.txt') f = open('/etc/postfix/virtual/vmailboxes.txt','w') f.write("".join(maps)) f.close() postmapCommand = "/usr/sbin/postmap /etc/postfix/virtual/vmailboxes.txt" postfixReload = "/usr/sbin/postfix reload" output = subprocess.Popen(postmapCommand.split(), stderr=subprocess.PIPE).communicate()[1] if output: raise "Could not run postmap: %s" % output output = subprocess.Popen(postfixReload.split(), stderr=subprocess.PIPE).communicate()[1] if output: raise "Could not reload Postfix: %s" % output return True except Exception, e: print "Could not compile maps: %s" % e def exists(self): """ Verifies if the email address exists. Returns True """ found = 0 f = open('/etc/postfix/virtual/domains/%s' % self.domain) for line in f: line = line.rstrip() (addy, mapp) = line.split() if addy == self.address: found = 1 break f.close() if found: return True else: return False def createAddress(self, password, owner): """ Creates the email address if it does not exist. Takes a password and an owner for an argument. Owner should be a system shell account. Returns True if created, False if problem """ if not self.exists(): try: # Create the entry in the maps file addressMap = "%s/%s/" % (self.domain, self.name) self._backup("/etc/postfix/virtual/domains/%s" % self.domain) f = open("/etc/postfix/virtual/domains/%s" % self.domain, 'a') f.write("%s\t%s\n" % (self.address, addressMap)) f.close() # Create the password entry encPassword = self._createPassword(password) self._backup('/etc/postfix/virtual/passwd') f = open('/etc/postfix/virtual/passwd', 'a') f.write("%s:%s:%s\n" % (self.address, encPassword, owner)) f.close() # Compile the maps self._compileMaps() # Create the mailbox mailboxPath = "/var/spool/vmail/%s/%s" % (self.domain, self.name) os.mkdir(mailboxPath) os.chown(mailboxPath, self.vmailUID, self.vmailGID) for directory in ["new", "cur", "tmp"]: os.mkdir("%s/%s" % (mailboxPath, directory)) os.chown("%s/%s" % (mailboxPath, directory), self.vmailUID, self.vmailGID) os.chmod("%s/%s" % (mailboxPath, directory), 0770) return True except Exception, e: print "Could not add email address: %s" % e else: print "Email address already exists!" def changePassword(self, password): """ Changes the password of the email address in question. Returns True if successful """ if self.exists(): try: # Create the new password hash encPassword = self._createPassword(password) # Get all password entries # Change the one in question while looping passwords = [] f = open('/etc/postfix/virtual/passwd') for line in f: (addy, pw, owner) = line.split(':') if addy == self.address: pw = encPassword passwords.append("%s:%s:%s" % (addy, pw, owner)) f.close() self._backup('/etc/postfix/virtual/passwd') f = open('/etc/postfix/virtual/passwd','w') f.write("".join(passwords)) f.close() return True except Exception, e: print "Could not change password: %s" % e else: print "Email address does not exist!" def deleteAddress(self): """ Deletes the given address. Returns True if successful """ if self.exists(): try: # Remove the password entry passwords = [] f = open('/etc/postfix/virtual/passwd') for line in f: (addy, pw, owner) = line.split(':') if addy != self.address: passwords.append(line) f.close() self._backup('/etc/postfix/virtual/passwd') f = open('/etc/postfix/virtual/passwd', 'w') f.write("".join(passwords)) f.close() # Remove the map maps = [] f = open("/etc/postfix/virtual/domains/%s" % self.domain) for line in f: (addy, mapp) = line.split() if addy != self.address: maps.append(line) f.close() self._backup("/etc/postfix/virtual/domains/%s" % self.domain) f = open("/etc/postfix/virtual/domains/%s" % self.domain, 'w') f.write("".join(maps)) f.close() # Compile the map file self._compileMaps() # backup the mailbox and then remove it backupCommand = "/bin/tar czf /var/spool/vmail/archive/%s.%s.tar.gz /var/spool/vmail/%s/%s" % (self.name, self.domain, self.domain, self.name) removeCommand = "rm -rf /var/spool/vmail/%s/%s" % (self.domain, self.name) output = subprocess.Popen(backupCommand.split(), stderr=subprocess.PIPE).communicate()[1] output = subprocess.Popen(removeCommand.split(), stderr=subprocess.PIPE).communicate()[1] if output: raise "Could not remove mailbox: %s" % output return True except Exception, e: print "Could not delete address: %s" % e else: print "Email address does not exist!" def spaceUsed(self): """ Returns the amount of space used in Kilobyes Just uses a shell command for du """ if self.exists(): try: duCommand = "du -s /var/spool/vmail/%s/%s" % (self.domain, self.name) output = subprocess.Popen(duCommand.split(), stdout=subprocess.PIPE).communicate()[0] return output.split()[0] except Exception, e: print "Could not get mailbox space: %s" % e else: print "Address does not exist!" def suspend(self): """ Suspends the account. Just tags a * at the end of the password Returns True if successful """ if self.exists(): try: passwords = [] f = open('/etc/postfix/virtual/passwd') for line in f: (addy, pw, owner) = line.split(':') if addy == self.address: pw = pw + "*" passwords.append("%s:%s:%s" % (addy,pw,owner)) f.close() self._backup('/etc/postfix/virtual/passwd') f = open('/etc/postfix/virtual/passwd','w') f.write("".join(passwords)) f.close() return True except Exception, e: print "Could not suspend account: %s" % e else: print "Address does not exist!" def unsuspend(self): """ Unsuspends the account. Removes the * at the end of the password Returns True if successful """ if self.exists(): try: passwords = [] f = open('/etc/postfix/virtual/passwd') for line in f: (addy, pw, owner) = line.split(':') if addy == self.address: if pw[-1] == '*': pw = pw[:-1] passwords.append("%s:%s:%s" % (addy,pw,owner)) f.close() self._backup('/etc/postfix/virtual/passwd') f = open('/etc/postfix/virtual/passwd','w') f.write("".join(passwords)) f.close() return True except Exception, e: print "Could not suspend account: %s" % e else: print "Address does not exist!"