1
0
Fork 0

- remove domain specific code from mail tools

- make mail aliases and pop3 accounts domain properties
- add class for system users
- move GNVDomain class to gnuviech package
- add more logging
- add password hashing, passwd and shadow functions to gnuviech.tools


git-svn-id: file:///home/www/usr01/svn/gnuviechadmin/gnuviech.info/gnuviechadmin/trunk@87 a67ec6bc-e5d5-0310-a910-815c51eb3124
This commit is contained in:
Jan Dittberner 2004-12-26 19:29:32 +00:00
parent b9ec217d1e
commit fa6961463e
6 changed files with 316 additions and 76 deletions

View file

@ -8,7 +8,7 @@ from log4py import Logger, FileAppender, LOGLEVEL_DEBUG
class GNVPrefs: class GNVPrefs:
"""This class has static variables for the settings of the GNUViech """This class has static variables for the settings of the GNUViech
administration tool. These settings can be customized in the file administration tool. These settings may be customized in the file
gvadm.preferences.""" gvadm.preferences."""
# define standard values # define standard values
PWDMINLENGTH = 6 PWDMINLENGTH = 6
@ -31,10 +31,33 @@ class GNVPrefs:
WEBSTATSDIR = WEBHOMEDIR+"stats/" WEBSTATSDIR = WEBHOMEDIR+"stats/"
LOGDIR = BASEPREFIX+"/var/log" LOGDIR = BASEPREFIX+"/var/log"
LOGFILE = LOGDIR+"/gnvadm.log" LOGFILE = LOGDIR+"/gnvadm.log"
USERTYPES = {
"web" : {
"minuid" : 10000,
"maxuid" : 10999,
"group" : "wwwusers",
"fullname" : "Webuser %s",
"home" : WEBHOMEDIR + "%s",
"shell" : "/bin/true",
"nohome" : 1,
"disabledpass" : 1
},
"pop3" : {
"minuid" : 20000,
"maxuid" : 29999,
"group" : "poponly",
"fullname" : "Popuser %s",
"home" : POPHOMEDIR + "%s",
"shell" : "/bin/true",
"nohome" : 1,
"disabledpass" : 1
}
}
# load custom settings # load custom settings
execfile("gvadm.preferences") execfile("gvadm.preferences")
def __init__(self): def __init__(self):
self.logger = self.getLogger(self)
self.setupDirs() self.setupDirs()
def __repr__(self): def __repr__(self):
@ -76,6 +99,30 @@ class GNVPrefs:
logger.set_loglevel(LOGLEVEL_DEBUG) logger.set_loglevel(LOGLEVEL_DEBUG)
return logger return logger
def getNextSysId(self, type):
nextid = self.USERTYPES[type]["minuid"]
file = open(self.BASEPREFIX+"/etc/passwd", "r")
for line in file.readlines():
pwdline = tools.splitPasswdLine(line)
self.logger.debug(str(pwdline))
uid = int(pwdline["uid"])
if (uid in
range(int(self.USERTYPES[type]["minuid"]),
int(self.USERTYPES[type]["maxuid"]))
and nextid <= uid): nextid = uid+1
return nextid
def getGroupId(self, type): pass
def getFullName(self, type, username):
return self.USERTYPES[type]["fullname"] % username
def getHomeDir(self, type, username):
return self.USERTYPES[type]["home"] % username
def getShell(self, type):
return self.USERTYPES[type]["shell"]
class NoAdmDirError(Exception): class NoAdmDirError(Exception):
"""This exception is raised if the admin directory does'nt exist.""" """This exception is raised if the admin directory does'nt exist."""
pass pass

View file

@ -4,8 +4,9 @@
Germany Germany
""" """
import os import os, pwd
import gnuviech import gnuviech
from gnuviech import sysuser
class DomainNotExistentError(Exception): pass class DomainNotExistentError(Exception): pass
@ -14,19 +15,27 @@ class DomainFileNotExistentError(Exception): pass
class GNVDomain: class GNVDomain:
"""Represents a domain in the GNUViech admin tool""" """Represents a domain in the GNUViech admin tool"""
def __init__(self, domain): def __init__(self, domain, prefs):
"""Initializes the domain object""" """Initializes the domain object"""
self.logger = prefs.getLogger(self)
self.prefs = prefs
self.name = domain self.name = domain
self.webaccount = None
self.zone = None
self.statsusers = {}
self.mailaliases = {}
self.pop3accounts = {}
try: try:
self.findUser() self.__findUser()
except gnuviech.NoAdmDirError: except gnuviech.NoAdmDirError:
gnuviech.setupDirs() prefs.setupDirs()
self.__init__(domain) self.__init__(domain)
except DomainFileNotExistentError: except DomainFileNotExistentError:
self.createDomainFile() self.__createDomainFile()
self.__init__(domain) self.__init__(domain, prefs)
except DomainNotExistentError: except DomainNotExistentError:
self.createUser() self.__createUser()
self.createWebUser()
def __repr__(self): def __repr__(self):
retval = "Domain "+self.name retval = "Domain "+self.name
@ -36,36 +45,36 @@ class GNVDomain:
retval += ", new domain" retval += ", new domain"
return retval return retval
def createDomainFile(self): def __createDomainFile(self):
"""Create the domain user id map file.""" """Create the domain user id map file."""
file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "w") file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "w")
file.close() file.close()
def createUser(self): def __createUser(self):
"""Create a user for the domain.""" """Create a user for the domain."""
file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "r") file = open(self.prefs.GVADMDIR+"domains", "r")
id = 0 id = 0
for line in file.readlines(): for line in file.readlines():
(key, value) = line.split(":") (key, value) = line.split(":")
if (int(value) > id): id = int(value) if (int(value) > id): id = int(value)
file.close() file.close()
id += 1 id += 1
file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "a") file = open(self.prefs.GVADMDIR+"domains", "a")
file.write("%s:%d\n" % (self.name, id)) file.write("%s:%d\n" % (self.name, id))
file.close() file.close()
self.findUser() self.__findUser()
def findUser(self): def __findUser(self):
"""Finds the user for the domain.""" """Finds the user for the domain."""
self.username = None self.username = None
if (os.access(gnuviech.GNVPrefs.GVADMDIR, os.R_OK)): if (os.access(self.prefs.GVADMDIR, os.R_OK)):
try: try:
domainsfile = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "r") domainsfile = open(self.prefs.GVADMDIR+"domains", "r")
for line in domainsfile.readlines(): for line in domainsfile.readlines():
(key, value) = line.split(":") (key, value) = line.split(":")
if (key == self.name): if (key == self.name):
self.username = "%s%02d" % ( self.username = "%s%02d" % (
gnuviech.GNVPrefs.USERPREFIX, self.prefs.USERPREFIX,
int(value)) int(value))
domainsfile.close() domainsfile.close()
if self.username is None: if self.username is None:
@ -96,6 +105,48 @@ class GNVDomain:
if (usertype == "pop3"): if (usertype == "pop3"):
return "%sp%d" % (self.username, self.getMaxPop3Id()+1) return "%sp%d" % (self.username, self.getMaxPop3Id()+1)
def addPOP3Account(self, account):
self.pop3accounts[account.localpart] = account
def addMailAlias(self, alias):
self.mailaliases[alias.localpart] = alias
def createWebUser(self):
try:
self.webaccount = sysuser.SystemUser(self.prefs, self.username)
except sysuser.UserNotInPasswdError:
self.webaccount = sysuser.createUser(self.prefs, self.username,
"web")
self.logger.debug(str(self.webaccount))
# #!/bin/sh
# . /usr/local/etc/preferences
# if [ -n $USERPREFIX ]; then
# USERPREFIX="usr"
# fi
# if [ $1 == "" ]; then
# echo "usage: $0 <usernum>"
# exit
# fi
# NEWUSER="$USERPREFIX$1"
# NEWHOME="/home/www/$NEWUSER"
# LOGDIR="/home/www/logfiles/$NEWUSER"
# adduser --home "$NEWHOME" --shell /bin/true --no-create-home --firstuid 10000 --ingroup wwwusers --disabled-password --gecos "Webuser $NEWUSER" $NEWUSER
# echo "${NEWUSER}:${NEWPASS}" | chpasswd
# mkdir -p "$NEWHOME/"{html,cgi-bin}
# mkdir -p "$LOGDIR"
# chown -Rc www-data.www-data "$LOGDIR"
# chmod 0750 "$LOGDIR"
# chown -Rc $NEWUSER.wwwusers "$NEWHOME"
# mkdir -p "$NEWHOME/html/stats"
# chown modlogan.wwwusers "$NEWHOME/html/stats"
# htpasswd -bc "/home/www/${NEWUSER}stats" "${NEWUSER}" "${NEWPASS}"
# echo added new web user $NEWUSER with password $NEWPASS
if __name__ == "__main__": if __name__ == "__main__":
dom = GNVDomain("dittberner.info") dom = GNVDomain("dittberner.info")
print dom print dom

View file

@ -0,0 +1,62 @@
from gnuviech import tools
class UserNotInPasswdError(Exception): pass
class NoPasswordInShadowError(Exception): pass
class SystemUser:
def __init__(self, prefs, username):
self.prefs = prefs
self.logger = prefs.getLogger(self)
self.getUser(username)
self.logger.debug(str(self))
def getUser(self, username):
pwdfile = open(self.prefs.BASEPREFIX+"/etc/passwd", "r")
for line in pwdfile.readlines():
pwdline = tools.splitPasswdLine(line)
self.logger.debug("PWDLINE: %s" % pwdline)
if pwdline["loginname"] == username:
self.username = pwdline["loginname"]
self.password = self.getPassword()
self.uid = pwdline["uid"]
self.gid = pwdline["gid"]
self.fullname = pwdline["fullname"]
self.homedir = pwdline["homedir"]
self.shell = pwdline["shell"]
return
pwdfile.close()
raise UserNotInPasswdError
def getPassword(self):
shadowfile = open(self.prefs.BASEPREFIX+"/etc/shadow", "r")
for line in shadowfile.readlines():
shadowline = tools.splitShadowLine(line)
self.logger.debug("SHADOWLINE: %s" % shadowline)
if shadowline["loginname"] == self.username:
shadowfile.close()
return shadowline["passwordhash"]
shadowfile.close()
raise NoPasswordInShadowError
def createUser(prefs, username, type):
line = ":".join((username, "x",
str(prefs.getNextSysId(type)),
str(prefs.getGroupId(type)),
prefs.getFullName(type, username),
prefs.getHomeDir(type, username),
prefs.getShell(type)))
passwdfile = open(prefs.BASEPREFIX+"/etc/passwd", "a")
passwdfile.write("%s\n" % line)
passwdfile.close()
createShadowItem(prefs, username, type, tools.generatePassword())
return SystemUser(prefs, username)
def createShadowItem(prefs, username, type, password):
line = ":".join((username,
tools.hashPassword(password, "md5"),
str(tools.daysSince1970()),
"0", "99999", "7", "", "", ""))
shadowfile = open(prefs.BASEPREFIX+"/etc/shadow", "a")
shadowfile.write("%s\n" % line)
shadowfile.close()

View file

@ -5,16 +5,62 @@
import random, re import random, re
from gnuviech import GNVPrefs from gnuviech import GNVPrefs
from crypt import crypt
from time import time
def generatePassword(): def generatePassword():
"""Generates a password from the chars in GNVPrefs.PWDCHARS with
a length between GNVPrefs.PWDMINLENGTH and GNVPrefs.PWDMAXLENGTH."""
return "".join([chr(char) for char in return "".join([chr(char) for char in
random.sample(GNVPrefs.PWDCHARS, random.sample(GNVPrefs.PWDCHARS,
random.randint(GNVPrefs.PWDMINLENGTH, random.randint(GNVPrefs.PWDMINLENGTH,
GNVPrefs.PWDMAXLENGTH))]) GNVPrefs.PWDMAXLENGTH))])
def generateSalt():
saltchars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
salt = []
for i in range(8):
salt.append(saltchars[random.randint(0, len(saltchars) - 1)])
return "".join(salt)
def checkEmail(email): def checkEmail(email):
"""Returns a match object if the given email address is syntactically """Returns a match object if the given email address is syntactically
correct otherwise it returns None""" correct otherwise it returns None"""
# regex for email check # regex for email check
p = re.compile(r'^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9\-]+(\.|[a-zA-Z0-9\-]+)*\.[a-z]{2,5})$') p = re.compile(r'^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9\-]+(\.|[a-zA-Z0-9\-]+)*\.[a-z]{2,5})$')
return p.search(email) return p.search(email)
def splitPasswdLine(line):
loginname, password, uid, gid, fullname, directory, shell = line.split(":")
return {
"loginname" : loginname,
"password" : password,
"uid" : uid,
"gid" : gid,
"fullname" : fullname,
"homedir" : directory,
"shell" : shell
}
def splitShadowLine(line):
(loginname, passwordhash, lastchange, maychange, mustchange, warnexpire,
disabled, disabledsince, reserved) = line.split(":")
return {
"loginname" : loginname,
"passwordhash" : passwordhash,
"lastchange" : lastchange,
"maychange" : maychange,
"mustchange" : mustchange,
"warnexpire" : warnexpire,
"disabled" : disabled,
"disabledsince" : disabledsince,
"reserved" : reserved
}
def hashPassword(password, method="md5"):
if (method == "md5"):
return crypt(password, "$1$%s" % generateSalt())
return crypt(password, generateSalt())
def daysSince1970():
return int(time()/(3600*24))

View file

@ -2,7 +2,9 @@
import os, string import os, string
import gnuviech import gnuviech
from GNVAdm import GNVDomain #from GNVAdm import GNVDomain
from gnuviech import GNVPrefs, tools
from gnuviech.gnvdomain import GNVDomain
# if [ -n $USERPREFIX ]; then # if [ -n $USERPREFIX ]; then
# USERPREFIX="usr" # USERPREFIX="usr"
@ -27,81 +29,107 @@ from GNVAdm import GNVDomain
# echo "Herzlich willkommen auf dem GNU-Viech" | mail -s "Willkommen auf dem GNU-Viech" ${NEWUSER} # echo "Herzlich willkommen auf dem GNU-Viech" | mail -s "Willkommen auf dem GNU-Viech" ${NEWUSER}
# echo added new pop3 user $NEWUSER with password $NEWPASS # echo added new pop3 user $NEWUSER with password $NEWPASS
class MailAliasExists(Exception): pass
class POP3AccountExists(Exception): pass
class MailAccount: class MailAccount:
def __init__(self, domain): def __init__(self, domain, localpart):
"Initialize a MailAccount instance for a given domain" "Initialize a MailAccount instance for a given domain"
if (not os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK & os.X_OK)): if (not os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK & os.X_OK)):
self.setupDirs() self.setupDirs()
self.domain = domain self.domain = domain
self.localpart = localpart
self.prefs = domain.prefs
self.logger = domain.prefs.getLogger(self)
def setupDirs(self): def __repr__(self):
os.mkdir(gnuviech.GNVPrefs.VIRTUALDOMDIR) return "%s@%s" % (self.localpart, self.domain.name)
class MailAlias(MailAccount): class MailAlias(MailAccount):
"""This represents a mail alias""" """This represents a mail alias"""
def __init__(self, domain): def __init__(self, domain, localpart, target):
"Initialize the POPAccount class for a given domain" "Initialize the POPAccount class for a given domain"
MailAccount.__init__(self, domain) if localpart in domain.mailaliases.keys():
self.aliases = {} raise MailAliasExists
self.readAll() MailAccount.__init__(self, domain, localpart)
self.setTarget(target)
def setTarget(self, target):
self.target = target
self.logger.debug("setting target for alias %s to %s." %
(str(self), self.target))
# self.aliases = {}
# self.readAll()
def readAll(self): # def readAll():
"""reads the aliasfile for the given domain""" # """reads the aliasfile for the given domain"""
self.aliases = {} # self.aliases = {}
if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK)): # if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK)):
try: # try:
aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name , 'r') # aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name , 'r')
for line in aliasfile.readlines(): # for line in aliasfile.readlines():
keyvals = string.split(line,":",1) # keyvals = string.split(line,":",1)
self.aliases[keyvals[0]] = keyvals[1].strip() # self.aliases[keyvals[0]] = keyvals[1].strip()
aliasfile.close() # aliasfile.close()
except IOError: # except IOError:
print "couldn't read the aliasfile for "+self.domain.name+"." # self.logger.error("couldn't read the aliasfile for "+self.domain.name+".")
else: # else:
print "couldn't read from "+gnuviech.GNVPrefs.VIRTUALDOMDIR+"." # self.logger.error("couldn't read from "+gnuviech.GNVPrefs.VIRTUALDOMDIR+".")
def writeAll(self): # def writeAll(self):
"""writes the aliasfile for the given domain with the aliases defined # """writes the aliasfile for the given domain with the aliases defined
in the dictionary object aliases""" # in the dictionary object aliases"""
if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.W_OK)): # if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.W_OK)):
try: # try:
aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name, 'w') # aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name, 'w')
keys = self.aliases.keys(); # keys = self.aliases.keys();
keys.sort(); # keys.sort();
for key in keys: # for key in keys:
aliasfile.write("%s:%s" % (key, self.aliases[key]) + "\n") # aliasfile.write("%s:%s" % (key, self.aliases[key]) + "\n")
aliasfile.close() # aliasfile.close()
except IOError: # except IOError:
print "writing to aliasfile failed." # self.logger.error("writing to aliasfile failed.")
else: # else:
print "no write access to directory "+gnuviech.GNVPrefs.VIRTUALDOMDIR+"." # self.logger.error("no write access to directory "+gnuviech.GNVPrefs.VIRTUALDOMDIR+".")
def setAlias(self, alias, target): # def setAlias(self, alias, target):
"""sets a mail alias for given domain which directs the MTA to the # """sets a mail alias for given domain which directs the MTA to the
given target # given target
""" # """
self.readAll() # self.readAll()
self.aliases[alias]=target # self.aliases[alias]=target
self.writeAll() # self.writeAll()
class POP3Account(MailAccount): class POP3Account(MailAccount):
"""This represents a pop 3 account""" """This represents a pop 3 account"""
def create(self, address): def __init__(self, domain, localpart):
"""Creates a pop3/imap account for the domain""" """Creates a new pop3 mail account"""
print self if localpart in domain.pop3accounts.keys():
print "adding address "+address raise POP3AccountExists
alias = MailAlias(self.domain) MailAccount.__init__(self, domain, localpart)
alias.setAlias(address, self.domain.getNextUser("pop3")) self.logger.debug("adding address %s@%s." % (self.localpart,
print alias self.domain.name))
self.setPassword(tools.generatePassword())
self.setSysUser(domain.getNextUser("pop3"))
self.domain.addMailAlias(MailAlias(self.domain,
self.localpart, self.sysuser))
def setPassword(self, newpassword):
self.password = newpassword
self.logger.debug("set password for %s to %s." %
(str(self), self.password))
def setSysUser(self, username):
self.sysuser = username
self.logger.debug("set system user for %s to %s" %
(str(self), self.sysuser))
if __name__ == "__main__": if __name__ == "__main__":
popacc = POP3Account(GNVDomain("dittberner.info")) prefs = GNVPrefs()
print popacc domain = GNVDomain("dittberner.info", prefs)
popacc.create("test") domain.addPOP3Account(POP3Account(domain, "test"))
domain.addMailAlias(MailAlias(domain, "klaus", "klaus@test.de"))
alias = MailAlias(GNVDomain("dittberner.info"))
print alias
alias.setAlias("klaus", "klaus@dittberner.info")
print alias

View file

@ -1,5 +1,6 @@
import gnuviech, sys import gnuviech, sys
import gnuviech.tools import gnuviech.tools
from gnuviech.gnvdomain import GNVDomain
class Test: class Test:
def __init__(self, prefs): def __init__(self, prefs):
@ -29,6 +30,11 @@ maximum password length: %d""" % (avglen, minlen, maxlen))
self.logger.debug("%s is a valid email address." % address) self.logger.debug("%s is a valid email address." % address)
else: else:
self.logger.debug("%s is an invalid email address." % address) self.logger.debug("%s is an invalid email address." % address)
domain = GNVDomain("dittberner.info", self.prefs)
self.logger.debug("Domain %s." % domain)
domain = GNVDomain("jesusgemeindesohland.de", self.prefs)
self.logger.debug("Domain %s." % domain)
if __name__ == "__main__": if __name__ == "__main__":
prefs = gnuviech.GNVPrefs() prefs = gnuviech.GNVPrefs()