diff --git a/backend/gnuviech/__init__.py b/backend/gnuviech/__init__.py index 5d0bcb6..b3fd3de 100644 --- a/backend/gnuviech/__init__.py +++ b/backend/gnuviech/__init__.py @@ -8,7 +8,7 @@ from log4py import Logger, FileAppender, LOGLEVEL_DEBUG class GNVPrefs: """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.""" # define standard values PWDMINLENGTH = 6 @@ -31,10 +31,33 @@ class GNVPrefs: WEBSTATSDIR = WEBHOMEDIR+"stats/" LOGDIR = BASEPREFIX+"/var/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 execfile("gvadm.preferences") def __init__(self): + self.logger = self.getLogger(self) self.setupDirs() def __repr__(self): @@ -76,6 +99,30 @@ class GNVPrefs: logger.set_loglevel(LOGLEVEL_DEBUG) 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): """This exception is raised if the admin directory does'nt exist.""" pass diff --git a/backend/GNVAdm.py b/backend/gnuviech/gnvdomain.py similarity index 53% rename from backend/GNVAdm.py rename to backend/gnuviech/gnvdomain.py index bc13a96..d63fef9 100644 --- a/backend/GNVAdm.py +++ b/backend/gnuviech/gnvdomain.py @@ -4,8 +4,9 @@ Germany """ -import os +import os, pwd import gnuviech +from gnuviech import sysuser class DomainNotExistentError(Exception): pass @@ -14,19 +15,27 @@ class DomainFileNotExistentError(Exception): pass class GNVDomain: """Represents a domain in the GNUViech admin tool""" - def __init__(self, domain): + def __init__(self, domain, prefs): """Initializes the domain object""" + self.logger = prefs.getLogger(self) + self.prefs = prefs self.name = domain + self.webaccount = None + self.zone = None + self.statsusers = {} + self.mailaliases = {} + self.pop3accounts = {} try: - self.findUser() + self.__findUser() except gnuviech.NoAdmDirError: - gnuviech.setupDirs() + prefs.setupDirs() self.__init__(domain) except DomainFileNotExistentError: - self.createDomainFile() - self.__init__(domain) + self.__createDomainFile() + self.__init__(domain, prefs) except DomainNotExistentError: - self.createUser() + self.__createUser() + self.createWebUser() def __repr__(self): retval = "Domain "+self.name @@ -36,36 +45,36 @@ class GNVDomain: retval += ", new domain" return retval - def createDomainFile(self): + def __createDomainFile(self): """Create the domain user id map file.""" file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "w") file.close() - def createUser(self): + def __createUser(self): """Create a user for the domain.""" - file = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "r") + file = open(self.prefs.GVADMDIR+"domains", "r") id = 0 for line in file.readlines(): (key, value) = line.split(":") if (int(value) > id): id = int(value) file.close() 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.close() - self.findUser() + self.__findUser() - def findUser(self): + def __findUser(self): """Finds the user for the domain.""" self.username = None - if (os.access(gnuviech.GNVPrefs.GVADMDIR, os.R_OK)): + if (os.access(self.prefs.GVADMDIR, os.R_OK)): try: - domainsfile = open(gnuviech.GNVPrefs.GVADMDIR+"domains", "r") + domainsfile = open(self.prefs.GVADMDIR+"domains", "r") for line in domainsfile.readlines(): (key, value) = line.split(":") if (key == self.name): self.username = "%s%02d" % ( - gnuviech.GNVPrefs.USERPREFIX, + self.prefs.USERPREFIX, int(value)) domainsfile.close() if self.username is None: @@ -96,6 +105,48 @@ class GNVDomain: if (usertype == "pop3"): 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 " +# 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__": dom = GNVDomain("dittberner.info") print dom diff --git a/backend/gnuviech/sysuser.py b/backend/gnuviech/sysuser.py new file mode 100644 index 0000000..3b5bb39 --- /dev/null +++ b/backend/gnuviech/sysuser.py @@ -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() diff --git a/backend/gnuviech/tools.py b/backend/gnuviech/tools.py index 827b495..b42e317 100644 --- a/backend/gnuviech/tools.py +++ b/backend/gnuviech/tools.py @@ -5,16 +5,62 @@ import random, re from gnuviech import GNVPrefs +from crypt import crypt +from time import time 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 random.sample(GNVPrefs.PWDCHARS, random.randint(GNVPrefs.PWDMINLENGTH, 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): """Returns a match object if the given email address is syntactically correct otherwise it returns None""" # regex for email check p = re.compile(r'^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9\-]+(\.|[a-zA-Z0-9\-]+)*\.[a-z]{2,5})$') 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)) diff --git a/backend/mailtools.py b/backend/mailtools.py index 487156c..2bb079b 100755 --- a/backend/mailtools.py +++ b/backend/mailtools.py @@ -2,7 +2,9 @@ import os, string import gnuviech -from GNVAdm import GNVDomain +#from GNVAdm import GNVDomain +from gnuviech import GNVPrefs, tools +from gnuviech.gnvdomain import GNVDomain # if [ -n $USERPREFIX ]; then # 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 added new pop3 user $NEWUSER with password $NEWPASS +class MailAliasExists(Exception): pass + +class POP3AccountExists(Exception): pass + class MailAccount: - def __init__(self, domain): + def __init__(self, domain, localpart): "Initialize a MailAccount instance for a given domain" if (not os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK & os.X_OK)): self.setupDirs() self.domain = domain + self.localpart = localpart + self.prefs = domain.prefs + self.logger = domain.prefs.getLogger(self) - def setupDirs(self): - os.mkdir(gnuviech.GNVPrefs.VIRTUALDOMDIR) + def __repr__(self): + return "%s@%s" % (self.localpart, self.domain.name) class MailAlias(MailAccount): """This represents a mail alias""" - def __init__(self, domain): + def __init__(self, domain, localpart, target): "Initialize the POPAccount class for a given domain" - MailAccount.__init__(self, domain) - self.aliases = {} - self.readAll() + if localpart in domain.mailaliases.keys(): + raise MailAliasExists + 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): - """reads the aliasfile for the given domain""" - self.aliases = {} - if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK)): - try: - aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name , 'r') - for line in aliasfile.readlines(): - keyvals = string.split(line,":",1) - self.aliases[keyvals[0]] = keyvals[1].strip() - aliasfile.close() - except IOError: - print "couldn't read the aliasfile for "+self.domain.name+"." - else: - print "couldn't read from "+gnuviech.GNVPrefs.VIRTUALDOMDIR+"." +# def readAll(): +# """reads the aliasfile for the given domain""" +# self.aliases = {} +# if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.R_OK)): +# try: +# aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name , 'r') +# for line in aliasfile.readlines(): +# keyvals = string.split(line,":",1) +# self.aliases[keyvals[0]] = keyvals[1].strip() +# aliasfile.close() +# except IOError: +# self.logger.error("couldn't read the aliasfile for "+self.domain.name+".") +# else: +# self.logger.error("couldn't read from "+gnuviech.GNVPrefs.VIRTUALDOMDIR+".") - def writeAll(self): - """writes the aliasfile for the given domain with the aliases defined -in the dictionary object aliases""" - if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.W_OK)): - try: - aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name, 'w') - keys = self.aliases.keys(); - keys.sort(); - for key in keys: - aliasfile.write("%s:%s" % (key, self.aliases[key]) + "\n") - aliasfile.close() - except IOError: - print "writing to aliasfile failed." - else: - print "no write access to directory "+gnuviech.GNVPrefs.VIRTUALDOMDIR+"." +# def writeAll(self): +# """writes the aliasfile for the given domain with the aliases defined +# in the dictionary object aliases""" +# if (os.access(gnuviech.GNVPrefs.VIRTUALDOMDIR, os.W_OK)): +# try: +# aliasfile = open(gnuviech.GNVPrefs.VIRTUALDOMDIR+self.domain.name, 'w') +# keys = self.aliases.keys(); +# keys.sort(); +# for key in keys: +# aliasfile.write("%s:%s" % (key, self.aliases[key]) + "\n") +# aliasfile.close() +# except IOError: +# self.logger.error("writing to aliasfile failed.") +# else: +# self.logger.error("no write access to directory "+gnuviech.GNVPrefs.VIRTUALDOMDIR+".") - def setAlias(self, alias, target): - """sets a mail alias for given domain which directs the MTA to the -given target -""" - self.readAll() - self.aliases[alias]=target - self.writeAll() +# def setAlias(self, alias, target): +# """sets a mail alias for given domain which directs the MTA to the +# given target +# """ +# self.readAll() +# self.aliases[alias]=target +# self.writeAll() class POP3Account(MailAccount): """This represents a pop 3 account""" - def create(self, address): - """Creates a pop3/imap account for the domain""" - print self - print "adding address "+address - alias = MailAlias(self.domain) - alias.setAlias(address, self.domain.getNextUser("pop3")) - print alias + def __init__(self, domain, localpart): + """Creates a new pop3 mail account""" + if localpart in domain.pop3accounts.keys(): + raise POP3AccountExists + MailAccount.__init__(self, domain, localpart) + self.logger.debug("adding address %s@%s." % (self.localpart, + 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__": - popacc = POP3Account(GNVDomain("dittberner.info")) - print popacc - popacc.create("test") - - alias = MailAlias(GNVDomain("dittberner.info")) - print alias - alias.setAlias("klaus", "klaus@dittberner.info") - print alias + prefs = GNVPrefs() + domain = GNVDomain("dittberner.info", prefs) + domain.addPOP3Account(POP3Account(domain, "test")) + domain.addMailAlias(MailAlias(domain, "klaus", "klaus@test.de")) diff --git a/backend/test.py b/backend/test.py index e03c443..ccf01ce 100644 --- a/backend/test.py +++ b/backend/test.py @@ -1,5 +1,6 @@ import gnuviech, sys import gnuviech.tools +from gnuviech.gnvdomain import GNVDomain class Test: 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) else: 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__": prefs = gnuviech.GNVPrefs()