diff --git a/bin/gva b/bin/gva
index f4aebcd..927bfcb 100755
--- a/bin/gva
+++ b/bin/gva
@@ -24,7 +24,12 @@ import gnuviechadmin.cli.client
import gnuviechadmin.cli.sysuser
import gnuviechadmin.cli.domain
import gnuviechadmin.cli.record
-import sys
+import sys, os, logging.config
+
+logcfgs = ('gnuviechadmin/logging.cfg', '/etc/gnuviechadmin/logging.cfg',
+ os.path.expanduser('~/.gva-logging.cfg'))
+for cfg in [x for x in logcfgs if os.path.exists(x)]:
+ logging.config.fileConfig(cfg)
commands = [gnuviechadmin.cli.client.ClientCli,
gnuviechadmin.cli.sysuser.SysuserCli,
diff --git a/data/dbrepo/versions/3/3.py b/data/dbrepo/versions/3/3.py
new file mode 100644
index 0000000..c634354
--- /dev/null
+++ b/data/dbrepo/versions/3/3.py
@@ -0,0 +1,49 @@
+from sqlalchemy import *
+from migrate import *
+from gnuviechadmin.backend.settings import dbschema
+
+meta = BoundMetaData(migrate_engine)
+domain = Table('domain', meta, schema = dbschema, autoload = True)
+mailaccount = Table(
+ 'mailaccount', meta,
+ Column('mailaccountid', Integer, primary_key = True),
+ Column('domainid', Integer, ForeignKey('domain.domainid'),
+ nullable = False),
+ Column('mailaccount', String(12), nullable = False, unique = True),
+ Column('clearpass', String(64)),
+ Column('cryptpass', String(34)),
+ Column('uid', Integer, nullable = False),
+ Column('gid', Integer, nullable = False),
+ Column('home', String(128), nullable = False),
+ Column('spamcheck', Boolean, nullable = False, default = False),
+ Column('sajunkscore', Integer),
+ schema = dbschema
+ )
+mailaddress = Table(
+ 'mailaddress', meta,
+ Column('mailaddressid', Integer, primary_key = True),
+ Column('domainid', Integer, ForeignKey('domain.domainid'),
+ nullable = False),
+ Column('email', String(255), nullable = False),
+ UniqueConstraint('email', 'domainid'),
+ schema = dbschema
+ )
+mailtarget = Table(
+ 'mailtarget', meta,
+ Column('mailtargetid', Integer, primary_key = True),
+ Column('mailaddressid', Integer, ForeignKey('mailaddress.mailaddressid'),
+ nullable = False),
+ Column('target', String(128), nullable = False),
+ UniqueConstraint('target', 'mailaddressid'),
+ schema = dbschema
+ )
+
+def upgrade():
+ mailaccount.create()
+ mailaddress.create()
+ mailtarget.create()
+
+def downgrade():
+ mailtarget.drop()
+ mailaddress.drop()
+ mailaccount.drop()
diff --git a/data/templates/domain/index.html b/data/templates/domain/index.html
new file mode 100644
index 0000000..76f97c4
--- /dev/null
+++ b/data/templates/domain/index.html
@@ -0,0 +1,6 @@
+
+
Hier entsteht ein neuer Internetauftritt
+
+Hier entsteht der Internetauftritt für www.${domain}.
+
+
diff --git a/data/templates/domainconf/htaccess-stats b/data/templates/domainconf/htaccess-stats
new file mode 100644
index 0000000..0fea634
--- /dev/null
+++ b/data/templates/domainconf/htaccess-stats
@@ -0,0 +1,4 @@
+AuthType Basic
+AuthName "Statistics for ${domain}"
+AuthUserFile "${userfile}"
+require valid-user
diff --git a/data/templates/domainconf/modlogan.conf b/data/templates/domainconf/modlogan.conf
new file mode 100644
index 0000000..782be47
--- /dev/null
+++ b/data/templates/domainconf/modlogan.conf
@@ -0,0 +1,42 @@
+[global]
+includepath = /etc/modlogan
+include = modlogan.def.conf,global
+
+loadplugin = input_clf
+loadplugin = processor_web
+loadplugin = output_modlogan
+
+statedir = ${statsdir}
+
+incremental = 1
+enable_resolver = 1
+
+read_ahead_limit = 100
+
+[processor_web]
+# to group known bot attacks like Nimda and CodeRed
+include = group.url.conf,group_exploits
+# to use the german file extension descriptions uncomment the following
+#include = group.extension.conf,groupext_german
+# include the default config
+include = modlogan.def.conf,processor_web
+
+# to only have pages listed, not files
+#hideurl="\.(?i:gif|png|jpe?g|css|js|class|mid|swf|mp3|mpg)$$"
+# to not show people they're reading the stats more often than people their page...
+hideurl="^/stats"
+
+debug_searchengines = 1
+hidereferrer = "^http://([^.]+\.)*${domainesc}/"
+
+## configure the output generator
+[output_modlogan]
+include = modlogan.def.conf,output_modlogan
+hostname = ${domain}
+
+outputdir = ${statsdir}
+
+## configure the parser
+[input_clf]
+include = modlogan.def.conf,input_clf
+inputfile = -
diff --git a/data/templates/domainconf/vhost.conf b/data/templates/domainconf/vhost.conf
new file mode 100644
index 0000000..9f15ffa
--- /dev/null
+++ b/data/templates/domainconf/vhost.conf
@@ -0,0 +1,10 @@
+
+ ServerName www.${domain}
+ ServerAlias ${domain}
+
+ Alias /stats ${statsdir}
+ DocumentRoot ${docroot}
+
+ ErrorLog ${logdir}/error.log
+ CustomLog ${logdir}/access.log combined
+
\ No newline at end of file
diff --git a/data/templates/mails/create_client.txt b/data/templates/mails/create_client.txt
new file mode 100644
index 0000000..0988a6f
--- /dev/null
+++ b/data/templates/mails/create_client.txt
@@ -0,0 +1,8 @@
+A new client with the following data has been created:
+
+ Firstname : ${firstname}
+ Lastname : ${lastname}
+ Email : ${email}
+ Address : ${address1}
+ : ${zipcode} ${city}
+ Phone : ${phone}
diff --git a/data/templates/mails/create_domain.txt b/data/templates/mails/create_domain.txt
new file mode 100644
index 0000000..d876205
--- /dev/null
+++ b/data/templates/mails/create_domain.txt
@@ -0,0 +1,14 @@
+A new domain with the following data has been created:
+
+ System user : ${sysuser}
+ Domain name : ${domain}
+ Document root : ${docroot}
+ Statistics password : ${statspass}
+
+To enable the domain in apache use
+
+ sudo a2ensite ${domain}
+
+You can access statistics for the domain at
+
+ http://www.${domain}/stats/
diff --git a/data/templates/mails/create_sysuser.txt b/data/templates/mails/create_sysuser.txt
new file mode 100644
index 0000000..eb08125
--- /dev/null
+++ b/data/templates/mails/create_sysuser.txt
@@ -0,0 +1,8 @@
+A new system user has been created
+
+UID : ${uid}
+Client : ${firstname} ${lastname} <${email}>
+Username : ${username}
+Password : ${password}
+Home : ${home}
+Shell : ${shell}
diff --git a/gnuviechadmin/backend/BackendEntity.py b/gnuviechadmin/backend/BackendEntity.py
index 24fe6af..06a02e4 100644
--- a/gnuviechadmin/backend/BackendEntity.py
+++ b/gnuviechadmin/backend/BackendEntity.py
@@ -19,41 +19,35 @@
#
# Version: $Id$
-import ConfigParser, os
-from subprocess import *
-from sqlalchemy import *
+import smtplib, os, logging, tempfile
+from email.MIMEText import MIMEText
+from pyme import core
+from pyme.constants.sig import mode
+
+from settings import config
from gnuviechadmin.exceptions import *
+from subprocess import *
+import sqlalchemy
class BackendEntity(object):
"""This is the abstract base class for all backend entity classes."""
- def __init__(self, verbose = False):
+ def __init__(self, delegateto, verbose = False):
+ self.logger = logging.getLogger("%s.%s" % (
+ self.__class__.__module__, self.__class__.__name__))
+ self.delegateto = delegateto
self.verbose = verbose
- self.config = ConfigParser.ConfigParser()
- self.config.readfp(open('gnuviechadmin/defaults.cfg'))
- self.config.read(['gnuviechadmin/gva.cfg',
- os.path.expanduser('~/.gva.cfg')])
def __repr__(self):
- if self.verbose:
- cols = [col for col in \
- object_mapper(self).local_table.columns.keys()]
- format = "%(class)s:"
- format = format + ", ".join([col + "=%(" + col + ")s" for col in \
- cols])
- data = {'class' : self.__class__.__name__}
- else:
- cols = self._shortkeys
- format = ",".join("%(" + col + ")s" for col in cols)
- data = {}
- data.update(dict([(col, self.__getattribute__(col)) for col in cols]))
- return format % data
+ return self.delegateto.__repr__(verbose = self.verbose)
def sucommand(self, cmdline, pipedata = None):
"""Executes a command as root using the configured suwrapper
command. If a pipe is specified it is used as stdin of the
subprocess."""
- suwrapper = self.config.get('common', 'suwrapper')
+ self.logger.debug("sucommand called: %s (pipedata=%s)", cmdline,
+ str(pipedata))
+ suwrapper = config.get('common', 'suwrapper')
toexec = "%s %s" % (suwrapper, cmdline)
if pipedata:
p = Popen(toexec, shell = True, stdin=PIPE)
@@ -63,21 +57,80 @@ class BackendEntity(object):
sts = os.waitpid(p.pid, 0)
if self.verbose:
print "%s|%s: %d" % (pipedata, toexec, sts[1])
+ self.logger.info("%s|%s: %d", pipedata, toexec, sts[1])
else:
p = Popen(toexec, shell = True)
sts = os.waitpid(p.pid, 0)
if self.verbose:
print "%s: %s" % (toexec, sts[1])
+ self.logger.info("%s: %s", toexec, sts[1])
return sts[1]
+ def send_mail(self, subject, text):
+ """This method sends a mail with the given text and subject
+ and signs it usign GnuPG. If a public key of the recipient is
+ available the mail is encrypted."""
+ plain = core.Data(text)
+ sig = core.Data()
+ c = core.Context()
+ signer = config.get('common', 'mailfrom')
+ rcpt = config.get('common', 'mailto')
+ c.signers_clear()
+ for sigkey in [x for x in c.op_keylist_all(signer, 1)]:
+ if sigkey.can_sign:
+ c.signers_add(sigkey)
+ if not c.signers_enum(0):
+ raise Exception("No secret keys for signing available for %s." % (
+ signer))
+ keylist = []
+ for key in [x for x in c.op_keylist_all(rcpt, 1)]:
+ valid = 0
+ subkey = key.subkeys
+ while subkey:
+ keyid = subkey.keyid
+ if keyid == None:
+ break
+ can_encrypt = subkey.can_encrypt
+ valid += can_encrypt
+ subkey = subkey.next
+ if valid:
+ keylist.append(key)
+ if keylist:
+ c.set_armor(1)
+ c.op_encrypt_sign(keylist, 1, plain, sig)
+ else:
+ c.op_sign(plain, sig, mode.CLEAR)
+ sig.seek(0,0)
+
+ msg = MIMEText(sig.read())
+ msg['Subject'] = subject
+ msg['From'] = signer
+ msg['To'] = rcpt
+
+ s = smtplib.SMTP()
+ s.connect()
+ s.sendmail(signer, [rcpt], msg.as_string())
+ s.close()
+
def validate(self):
"""Validates whether all mandatory fields of the entity have
values."""
missingfields = []
for key in [col.name for col in \
- object_mapper(self).local_table.columns \
+ sqlalchemy.object_mapper(
+ self.delegateto).local_table.columns \
if not col.primary_key and not col.nullable]:
- if self.__getattribute__(key) is None:
+ if self.delegateto.__getattribute__(key) is None:
missingfields.append(key)
if missingfields:
raise MissingFieldsError(missingfields)
+
+ def write_to_file(self, filename, template):
+ """Write the data from template to the specified file."""
+ tmp = tempfile.NamedTemporaryFile()
+ tmp.write(template)
+ tmp.flush()
+ cmd = 'cp "%s" "%s"' % (tmp.name, filename)
+ self.sucommand(cmd)
+ tmp.close()
+
diff --git a/gnuviechadmin/backend/BackendEntityHandler.py b/gnuviechadmin/backend/BackendEntityHandler.py
index 3b3384b..32a83ae 100644
--- a/gnuviechadmin/backend/BackendEntityHandler.py
+++ b/gnuviechadmin/backend/BackendEntityHandler.py
@@ -19,51 +19,68 @@
#
# Version: $Id$
-from sqlalchemy import *
+import sqlalchemy, logging
from gnuviechadmin.exceptions import *
from BackendEntity import *
class BackendEntityHandler(object):
- def __init__(self, entityclass, verbose = False):
+ def __init__(self, entityclass, toclass, verbose = False):
+ self.logger = logging.getLogger("%s.%s" % (
+ self.__class__.__module__, self.__class__.__name__))
self.entityclass = entityclass
+ self.toclass = toclass
self.verbose = verbose
def create(self, **kwargs):
-# try:
- sess = create_session()
- entity = self.entityclass(self.verbose, **kwargs)
- try:
- entity.create_hook()
- sess.save(entity)
- sess.flush()
- except:
- sess.delete(entity)
- sess.flush()
- raise
-# except Exception, e:
-# raise CreationFailedError(self.entityclass.__name__, e)
+ """Create a new entity of the managed type with the fields set
+ to the values in kwargs."""
+ self.logger.debug("create with params %s", str(kwargs))
+ sess = sqlalchemy.create_session()
+ transaction = sess.create_transaction()
+ delegate = self.toclass(**kwargs)
+ entity = self.entityclass(delegate, self.verbose)
+ try:
+ sess.save(delegate)
+ sess.flush()
+ sess.refresh(delegate)
+ entity.create_hook(sess)
+ sess.flush()
+ transaction.commit()
+ except:
+ transaction.rollback()
+ self.logger.exception("Exception in create.")
+ raise
- def fetchall(self):
+ def fetchall(self, **kwargs):
"""Fetches all entities of the managed entity type."""
- session = create_session()
- query = session.query(self.entityclass)
- allentities = query.select()
- for entity in allentities:
- BackendEntity.__init__(entity, self.verbose)
- return allentities
+ self.logger.debug("fetchall with params %s", str(kwargs))
+ session = sqlalchemy.create_session()
+ query = session.query(self.toclass)
+ if kwargs:
+ allentities = query.select_by(**kwargs)
+ else:
+ allentities = query.select()
+ return [self.entityclass(entity, self.verbose) \
+ for entity in allentities]
def delete(self, pkvalue):
"""Deletes the entity of the managed entity type that has the
specified primary key value."""
+ self.logger.debug("delete with primary key %s", str(pkvalue))
+ sess = sqlalchemy.create_session()
+ transaction = sess.create_transaction()
try:
- sess = create_session()
- entity = sess.query(self.entityclass).get(pkvalue)
- if entity:
- BackendEntity.__init__(entity, self.verbose)
+ to = sess.query(self.toclass).get(pkvalue)
+ if to:
+ entity = self.entityclass(to, self.verbose)
+ self.logger.info("delete %s", str(entity))
if self.verbose:
print "delete %s" % (str(entity))
- entity.delete_hook()
- sess.delete(entity)
+ entity.delete_hook(sess)
+ sess.delete(to)
sess.flush()
+ transaction.commit()
except Exception, e:
- raise DeleteFailedError(self.entityclass.__name__, e)
+ transaction.rollback()
+ self.logger.exception("Exception in delete.")
+ raise
diff --git a/gnuviechadmin/backend/BackendTo.py b/gnuviechadmin/backend/BackendTo.py
new file mode 100644
index 0000000..397da3b
--- /dev/null
+++ b/gnuviechadmin/backend/BackendTo.py
@@ -0,0 +1,73 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright (C) 2007 by Jan Dittberner.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+#
+# Version: $Id$
+
+from sqlalchemy import object_mapper
+
+from tables import *
+
+class BackendTo(object):
+ """Backend transfer object class."""
+ def __init__(self, **kwargs):
+ for (key, value) in kwargs.items():
+ self.__setattr__(key, value)
+
+ def __repr__(self, **kwargs):
+ if 'verbose' in kwargs and kwargs['verbose']:
+ cols = [col for col in \
+ object_mapper(self).local_table.columns.keys()]
+ format = "%(class)s:"
+ format = format + ", ".join([col + "=%(" + col + ")s" for col in \
+ cols])
+ data = {'class' : self.__class__.__name__}
+ else:
+ cols = self._shortkeys
+ format = ",".join("%(" + col + ")s" for col in cols)
+ data = {}
+ data.update(dict([(col, self.__getattribute__(col)) for col in cols]))
+ return format % data
+
+class Client(BackendTo):
+ """Transfer object class for clients."""
+ _shortkeys = ('clientid', 'firstname', 'lastname', 'email')
+
+class Sysuser(BackendTo):
+ """Transfer object class for system users."""
+ _shortkeys = ("sysuserid", "clientid", "username", "home", "shell")
+
+class Domain(BackendTo):
+ """Transfer object class for DNS domains."""
+ _shortkeys = ("domainid", "sysuserid", "name", "type")
+
+class Record(BackendTo):
+ """Transfer object class for DNS domain records."""
+ _shortkeys = ("recordid", "domainid", "name", "type", "content")
+
+client_mapper = mapper(Client, client_table)
+sysuser_mapper = mapper(Sysuser, sysuser_table)
+domain_mapper = mapper(Domain, domain_table)
+record_mapper = mapper(Record, record_table)
+
+client_mapper.add_property("sysusers", relation(Sysuser, backref = 'client'))
+
+sysuser_mapper.add_property("domains", relation(Domain, backref = 'sysuser'))
+
+domain_mapper.add_property("records", relation(Record, cascade = 'all',
+ backref = 'domain'))
diff --git a/gnuviechadmin/backend/client.py b/gnuviechadmin/backend/client.py
index a310fe4..6af57db 100644
--- a/gnuviechadmin/backend/client.py
+++ b/gnuviechadmin/backend/client.py
@@ -19,42 +19,58 @@
#
# Version: $Id$
-from sqlalchemy import *
-from tables import client_table
from gnuviechadmin.exceptions import *
-
-import sysuser
+from settings import config
+from BackendTo import *
from BackendEntity import *
from BackendEntityHandler import *
-class Client(BackendEntity):
+class ClientEntity(BackendEntity):
"""Entity class for clients."""
- _shortkeys = ('clientid', 'firstname', 'lastname', 'email')
-
- def __init__(self, verbose = False, **kwargs):
- BackendEntity.__init__(self, verbose)
- self.clientid = None
- self.country = 'de'
- self.title = None
- self.address2 = None
- self.mobile = None
- self.fax = None
+ def __init__(self, delegate, verbose = False, **kwargs):
+ BackendEntity.__init__(self, delegate, verbose)
for (key, value) in kwargs.items():
self.__setattr__(key, value)
+ if not self.delegateto.country:
+ self.delegateto.country = self._get_default_country()
self.validate()
- def create_hook(self):
- pass
+ def _client_mail(self):
+ text = get_template(config.get('common', 'mailtemplates'),
+ config.get('client', 'create.mail')).substitute({
+ 'firstname' : self.delegateto.firstname,
+ 'lastname' : self.delegateto.lastname,
+ 'email' : self.delegateto.email,
+ 'address1' : self.delegateto.address1,
+ 'zipcode' : self.delegateto.zip,
+ 'city' : self.delegateto.city,
+ 'phone' : self.delegateto.phone})
+ subject = get_template_string(
+ config.get('client', 'create_subject')).substitute({
+ 'firstname' : self.delegateto.firstname,
+ 'lastname' : self.delegateto.lastname})
+ self.send_mail(subject, text)
- def delete_hook(self):
- pass
+ def create_hook(self, session):
+ """Actions to perform when a client is created."""
+ self._client_mail()
-client_mapper = mapper(Client, client_table)
-client_mapper.add_property("sysusers", relation(sysuser.Sysuser))
+ def delete_hook(self, session):
+ """Actions to perform when a client is deleted."""
+ if self.delegateto.sysusers:
+ raise CannotDeleteError(
+ self.delegateto,
+ "it still has the following system users assigned: %s" % (
+ ", ".join([sysuser.username for sysuser in \
+ self.delegateto.sysusers])))
+
+ def _get_default_country(self):
+ """Gets the default country."""
+ return config.get('common', 'defaultcountry')
class ClientHandler(BackendEntityHandler):
"""BackendEntityHandler for Client entities."""
def __init__(self, verbose = False):
- BackendEntityHandler.__init__(self, Client, verbose)
+ BackendEntityHandler.__init__(self, ClientEntity, Client, verbose)
diff --git a/gnuviechadmin/backend/domain.py b/gnuviechadmin/backend/domain.py
index bf7681e..2935762 100644
--- a/gnuviechadmin/backend/domain.py
+++ b/gnuviechadmin/backend/domain.py
@@ -19,37 +19,29 @@
#
# Version: $Id: client.py 1101 2007-02-28 21:15:20Z jan $
-from sqlalchemy import *
-from tables import domain_table
+import datetime, os
+
from gnuviechadmin.exceptions import *
+from settings import *
+from BackendTo import Record, Domain
+from BackendEntity import BackendEntity
+from BackendEntityHandler import BackendEntityHandler
-from record import Record
-import datetime
-from BackendEntity import *
-from BackendEntityHandler import *
-from settings import config
-
-class Domain(BackendEntity):
+class DomainEntity(BackendEntity):
"""Entity class for DNS domains."""
- _shortkeys = ("domainid", "sysuserid", "name", "type")
_valid_domain_types = ("MASTER", "SLAVE")
- def __init__(self, verbose = False, **kwargs):
- BackendEntity.__init__(self, verbose)
- self.domainid = None
- self.sysuserid = None
- self.name = None
- self.type = None
- self.master = None
+ def __init__(self, delegate, verbose = False, **kwargs):
+ BackendEntity.__init__(self, delegate, verbose)
self.ns1 = None
self.ns2 = None
self.mx = None
self.ipaddr = None
for (key, value) in kwargs.items():
self.__setattr__(key, value)
- if not self.type:
- self.type = self.getdefaultdomaintype()
+ if not self.delegateto.type:
+ self.delegateto.type = self.getdefaultdomaintype()
if not self.ns1:
self.ns1 = config.get('domain', 'defaultns1')
if not self.ns2:
@@ -58,7 +50,7 @@ class Domain(BackendEntity):
self.mx = config.get('domain', 'defaultmx')
if not self.ipaddr:
self.ipaddr = config.get('domain', 'defaultip')
- self.type = self.type.upper()
+ self.delegateto.type = self.delegateto.type.upper()
self.validate()
def getdefaultdomaintype(self):
@@ -66,13 +58,13 @@ class Domain(BackendEntity):
def validate(self):
BackendEntity.validate(self)
- if not self.type in self._valid_domain_types:
+ if not self.delegateto.type in self._valid_domain_types:
raise ValidationFailedError(
- self, "invalid domain type %s" % (self.type))
- if self.type == 'SLAVE' and not self.master:
+ self, "invalid domain type %s" % (self.delegateto.type))
+ if self.delegateto.type == 'SLAVE' and not self.delegateto.master:
raise ValidationFailedError(
self, "you have to specify a master for slave domains.")
- if self.type == 'MASTER':
+ if self.delegateto.type == 'MASTER':
if not self.ns1 or not self.ns2:
raise ValidationFailedError(
self, "two nameservers must be specified.")
@@ -80,10 +72,17 @@ class Domain(BackendEntity):
raise ValidationFailedError(
self, "a primary mx host must be specified.")
- def _getnewserial(self):
+ def _getnewserial(self, oldserial = None):
current = datetime.datetime.now()
- return int("%04d%02d%02d01" % \
- (current.year, current.month, current.day))
+ datepart = "%04d%02d%02d" % \
+ (current.year, current.month, current.day)
+ retval = None
+ if oldserial:
+ if str(oldserial)[:len(datepart)] == datepart:
+ retval = oldserial + 1
+ if not retval:
+ retval = int("%s01" % (datepart))
+ return retval
def _getnewsoa(self):
return '%s %s %d %d %d %d %d' % \
@@ -95,36 +94,228 @@ class Domain(BackendEntity):
config.getint('domain', 'defaultexpire'),
config.getint('domain', 'defaultminimumttl'))
- def create_hook(self):
- self.records.append(Record(
- name = self.name, type = 'SOA',
+ def update_serial(self, session):
+ query = session.query(Record)
+ soarecord = query.get_by(Record.c.type == 'SOA',
+ Record.c.domainid == self.delegateto.domainid)
+ parts = soarecord.content.split(" ")
+ parts[2] = str(self._getnewserial(int(parts[2])))
+ soarecord.content = " ".join(parts)
+ session.save(soarecord)
+ session.flush()
+
+ def _get_vhost_dir(self):
+ return os.path.join(self.delegateto.sysuser.home,
+ self.delegateto.name,
+ config.get('domain', 'htdir'))
+
+ def _get_log_dir(self):
+ return os.path.join(config.get('domain', 'logpath'),
+ self.delegateto.name)
+
+ def _get_stats_dir(self):
+ return os.path.join(config.get('domain', 'statspath'),
+ self.delegateto.name)
+
+ def _create_vhost_dir(self):
+ vhostdir = self._get_vhost_dir()
+ cmd = 'mkdir -p "%s"' % (vhostdir)
+ self.sucommand(cmd)
+ for tpl in [tpl for tpl in os.listdir(
+ get_template_dir(config.get('domain', 'htdocstemplate'))) \
+ if not tpl.startswith('.')]:
+ template = get_template(config.get('domain', 'htdocstemplate'),
+ tpl)
+ template = template.substitute({
+ 'domain' : self.delegateto.name})
+ self.write_to_file(os.path.join(vhostdir, tpl), template)
+ cmd = 'chown -R %(username)s:%(group)s "%(dir)s"' % {
+ 'username' : self.delegateto.sysuser.username,
+ 'group' : config.get('sysuser', 'defaultgroup'),
+ 'dir' : vhostdir}
+ self.sucommand(cmd)
+
+ def _create_log_dir(self):
+ cmd = 'mkdir -p "%s"' % (self._get_log_dir())
+ self.sucommand(cmd)
+
+ def _get_auth_userfile(self):
+ authdir = config.get('domain', 'authdir')
+ if not os.path.isdir(authdir):
+ cmd = 'mkdir -p "%s"' % (authdir)
+ self.sucommand(cmd)
+ return os.path.join(authdir, '%s.passwd' % (self.delegateto.name))
+
+ def _create_stats_dir(self):
+ """Creates the statistics directory for the domain and sets
+ Apache .htaccess password protection."""
+ statsdir = self._get_stats_dir()
+ authfile = self._get_auth_userfile()
+ cmd = 'htpasswd -m -c -b "%s" "%s" "%s"' % (
+ authfile,
+ self.delegateto.sysuser.username,
+ self.delegateto.sysuser.clearpass)
+ self.sucommand(cmd)
+ cmd = 'mkdir -p "%s"' % (statsdir)
+ self.sucommand(cmd)
+ template = get_template(config.get('domain', 'conftemplates'),
+ config.get('domain', 'statshtaccesstemplate'))
+ template = template.substitute({
+ 'domain' : self.delegateto.name,
+ 'userfile' : authfile})
+ self.write_to_file(os.path.join(self._get_stats_dir(),
+ '.htaccess'), template)
+
+ def _create_stats_conf(self):
+ modlogandir = os.path.join(config.get('domain',
+ 'modlogandir'),
+ self.delegateto.sysuser.username)
+ cmd = 'mkdir -p "%s"' % (modlogandir)
+ self.sucommand(cmd)
+ template = get_template(config.get('domain', 'conftemplates'),
+ config.get('domain', 'modlogantemplate'))
+ template = template.substitute({
+ 'statsdir' : self._get_stats_dir(),
+ 'logdir' : self._get_log_dir(),
+ 'domain' : self.delegateto.name,
+ 'domainesc' : self.delegateto.name.replace('.', '\.')})
+ self.write_to_file(os.path.join(modlogandir,
+ self.delegateto.name + '.conf'),
+ template)
+
+ def _create_apache_conf(self):
+ template = get_template(config.get('domain', 'conftemplates'),
+ config.get('domain', 'apachetemplate'))
+ template = template.substitute({
+ 'ipaddr' : self.ipaddr,
+ 'statsdir' : self._get_stats_dir(),
+ 'logdir' : self._get_log_dir(),
+ 'domain' : self.delegateto.name,
+ 'docroot' : self._get_vhost_dir()})
+ self.write_to_file(os.path.join(config.get('domain', 'sitesdir'),
+ self.delegateto.name), template)
+
+ def _mail_domain(self):
+ template = get_template(config.get('common', 'mailtemplates'),
+ config.get('domain', 'create.mail'))
+ text = template.substitute({
+ 'sysuser' : self.delegateto.sysuser.username,
+ 'domain' : self.delegateto.name,
+ 'docroot' : self._get_vhost_dir(),
+ 'statspass' : self.delegateto.sysuser.clearpass})
+ template = get_template_string(config.get('domain', 'create_subject'))
+ subject = template.substitute({
+ 'domain' : self.delegateto.name})
+ self.send_mail(subject, text)
+
+ def create_hook(self, session):
+ self.delegateto.records.append(Record(
+ name = self.delegateto.name, type = 'SOA',
content = self._getnewsoa(),
ttl = config.getint('domain', 'defaultttl')))
- self.records.append(Record(
- name = self.name, type = 'NS', content = self.ns1,
+ self.delegateto.records.append(Record(
+ name = self.delegateto.name, type = 'NS', content = self.ns1,
ttl = config.getint('domain', 'defaultttl')))
- self.records.append(Record(
- name = self.name, type = 'NS', content = self.ns2,
+ self.delegateto.records.append(Record(
+ name = self.delegateto.name, type = 'NS', content = self.ns2,
ttl = config.getint('domain', 'defaultttl')))
- self.records.append(Record(
- name = self.name, type = 'MX', content = self.mx,
+ self.delegateto.records.append(Record(
+ name = self.delegateto.name, type = 'MX', content = self.mx,
ttl = config.getint('domain', 'defaultttl'),
prio = config.getint('domain', 'defaultmxprio')))
- self.records.append(Record(
- name = self.name, type = 'A', content = self.ipaddr,
+ self.delegateto.records.append(Record(
+ name = self.delegateto.name, type = 'A', content = self.ipaddr,
ttl = config.getint('domain', 'defaultttl')))
- self.records.append(Record(
- name = "www.%s" % (self.name), type = 'A', content = self.ipaddr,
+ self.delegateto.records.append(Record(
+ name = "www.%s" % (self.delegateto.name), type = 'A',
+ content = self.ipaddr,
ttl = config.getint('domain', 'defaultttl')))
+ session.save(self.delegateto)
+ session.flush()
+ self._create_vhost_dir()
+ self._create_log_dir()
+ self._create_stats_dir()
+ self._create_stats_conf()
+ self._create_apache_conf()
+ self._mail_domain()
- def delete_hook(self):
- pass
+ def _delete_apache_conf(self):
+ cmd = 'a2dissite %s' % (self.delegateto.name)
+ self.sucommand(cmd)
+ cmd = 'rm "%s"' % (os.path.join(config.get('domain', 'sitesdir'),
+ self.delegateto.name))
+ self.sucommand(cmd)
-domain_mapper = mapper(Domain, domain_table)
-domain_mapper.add_property("records", relation(Record))
+ def _delete_stats_conf(self):
+ cmd = 'rm "%s"' % (os.path.join(config.get('domain', 'modlogandir'),
+ self.delegateto.sysuser.username,
+ self.delegateto.name + '.conf'))
+ self.sucommand(cmd)
+
+ def _archive_stats_dir(self):
+ archive = os.path.join(self.delegateto.sysuser.home,
+ '%(domain)s-stats.tar.gz' % {
+ 'domain' : self.delegateto.name})
+ cmd = 'tar czf "%(archive)s" --directory="%(statsbase)s" "%(statsdir)s"' % {
+ 'archive' : archive,
+ 'statsbase' : config.get('domain', 'statspath'),
+ 'statsdir' : self.delegateto.name}
+ self.sucommand(cmd)
+ cmd = 'rm -r "%(statsdir)s"' % {
+ 'statsdir' : self._get_stats_dir()}
+ self.sucommand(cmd)
+ cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
+ 'username' : self.delegateto.sysuser.username,
+ 'group' : config.get('sysuser', 'defaultgroup'),
+ 'archive' : archive}
+ self.sucommand(cmd)
+
+ def _archive_log_dir(self):
+ archive = os.path.join(self.delegateto.sysuser.home,
+ '%(domain)s-logs.tar.gz' % {
+ 'domain' : self.delegateto.name})
+ cmd = 'tar czf "%(archive)s" --directory="%(logbase)s" "%(logdir)s"' % {
+ 'archive' : archive,
+ 'logbase' : config.get('domain', 'logpath'),
+ 'logdir' : self.delegateto.name}
+ self.sucommand(cmd)
+ cmd = 'rm -r "%(logdir)s"' % {
+ 'logdir' : self._get_log_dir()}
+ self.sucommand(cmd)
+ cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
+ 'username' : self.delegateto.sysuser.username,
+ 'group' : config.get('sysuser', 'defaultgroup'),
+ 'archive' : archive}
+ self.sucommand(cmd)
+
+ def _archive_vhost_dir(self):
+ archive = os.path.join(self.delegateto.sysuser.home,
+ '%(domain)s-vhost.tar.gz' % {
+ 'domain' : self.delegateto.name})
+ cmd = 'tar czf "%(archive)s" --directory="%(vhostbase)s" "%(vhostdir)s"' % {
+ 'archive' : archive,
+ 'vhostbase' : self.delegateto.sysuser.home,
+ 'vhostdir' : self.delegateto.name}
+ self.sucommand(cmd)
+ cmd = 'rm -r "%(vhostdir)s"' % {
+ 'vhostdir' : os.path.join(self.delegateto.sysuser.home,
+ self.delegateto.name)}
+ self.sucommand(cmd)
+ cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
+ 'username' : self.delegateto.sysuser.username,
+ 'group' : config.get('sysuser', 'defaultgroup'),
+ 'archive' : archive}
+ self.sucommand(cmd)
+
+ def delete_hook(self, session):
+ self._delete_apache_conf()
+ self._delete_stats_conf()
+ self._archive_stats_dir()
+ self._archive_log_dir()
+ self._archive_vhost_dir()
class DomainHandler(BackendEntityHandler):
"""BackendEntityHandler for Domain entities."""
def __init__(self, verbose = False):
- BackendEntityHandler.__init__(self, Domain, verbose)
+ BackendEntityHandler.__init__(self, DomainEntity, Domain, verbose)
diff --git a/gnuviechadmin/backend/record.py b/gnuviechadmin/backend/record.py
index 7608428..93b023e 100644
--- a/gnuviechadmin/backend/record.py
+++ b/gnuviechadmin/backend/record.py
@@ -19,37 +19,24 @@
#
# Version: $Id$
-from sqlalchemy import *
-from tables import record_table
from gnuviechadmin.exceptions import *
-
+from BackendTo import Record, Domain
+from domain import DomainEntity
from BackendEntity import *
from BackendEntityHandler import *
-class Record(object):
+class RecordEntity(BackendEntity):
"""Entity class for DNS domain records."""
- def __init__(self, **kwargs):
- for (key, value) in kwargs.items():
- self.__setattr__(key, value)
-
- #_shortkeys = ("recordid", "domainid", "name", "type", "content")
-
- #def __init__(self, verbose = False, **kwargs):
- # BackendEntity.__init__(self, verbose)
- # self.recordid = None
- # self.domainid = None
- # self.name = None
- # self.type = None
- # self.content = None
- # self.ttl = None
- # self.prio = None
- # self.change_date = None
- # self.validate()
-
-record_mapper = mapper(Record, record_table)
+ def create_hook(self, session):
+ domain = session.load(Domain, self.delegateto.domainid)
+ DomainEntity(domain).update_serial(session)
+
+ def delete_hook(self, session):
+ domain = session.load(Domain, self.delegateto.domainid)
+ DomainEntity(domain).update_serial(session)
class RecordHandler(BackendEntityHandler):
"""BackendEntityHandler for Record entities."""
def __init__(self, verbose = False):
- BackendEntityHandler.__init__(self, Record, verbose)
+ BackendEntityHandler.__init__(self, RecordEntity, Record, verbose)
diff --git a/gnuviechadmin/backend/settings.py b/gnuviechadmin/backend/settings.py
index cb11513..afac9fe 100644
--- a/gnuviechadmin/backend/settings.py
+++ b/gnuviechadmin/backend/settings.py
@@ -19,13 +19,28 @@
#
# Version: $Id$
-import ConfigParser, os
+import ConfigParser, os, string, logging.config
# global settings which must not be user configurable
-required_version = 2
+required_version = 3
dbschema = 'gva'
# load user configuration
config = ConfigParser.ConfigParser()
config.readfp(open('gnuviechadmin/defaults.cfg'))
config.read(['gnuviechadmin/gva.cfg', os.path.expanduser('~/.gva.cfg')])
+
+def get_template_dir(dirname):
+ """Returns the template directory for the given directory."""
+ templatepath = config.get('common', 'templatedir')
+ return os.path.join(templatepath, dirname)
+
+def get_template(dirname, filename):
+ """Returns the template data from the given template file."""
+ templatefile = file(os.path.join(get_template_dir(dirname),
+ filename))
+ return string.Template(templatefile.read())
+
+def get_template_string(templatestring):
+ """Returns a template object for the given template string."""
+ return string.Template(templatestring)
diff --git a/gnuviechadmin/backend/sysuser.py b/gnuviechadmin/backend/sysuser.py
index fd625bd..6c1c4e4 100644
--- a/gnuviechadmin/backend/sysuser.py
+++ b/gnuviechadmin/backend/sysuser.py
@@ -19,120 +19,123 @@
#
# Version: $Id$
-from sqlalchemy import *
-from tables import sysuser_table
from gnuviechadmin.exceptions import *
from gnuviechadmin.util import passwordutils, getenttools
-
+from settings import config
+from BackendTo import *
from BackendEntity import *
from BackendEntityHandler import *
-class Sysuser(BackendEntity):
+class SysuserEntity(BackendEntity):
"""Entity class for system users."""
- _shortkeys = ("sysuserid", "clientid", "username", "home", "shell")
-
- def __init__(self, verbose = False, **kwargs):
- BackendEntity.__init__(self, verbose)
- self.sysuserid = None
- self.username = None
- self.usertype = None
- self.home = None
- self.shell = None
- self.clearpass = None
- self.md5pass = None
- self.clientid = None
- self.sysuid = None
+ def __init__(self, delegate, verbose = False, **kwargs):
+ BackendEntity.__init__(self, delegate, verbose)
for (key, value) in kwargs.items():
self.__setattr__(key, value)
- if not self.username:
- self.username = self.getnextsysusername()
- if not self.usertype:
- self.usertype = self.getdefaultsysusertype()
- if not self.home:
- self.home = self.gethome(self.username)
- if not self.shell:
- self.shell = self.getdefaultshell()
- (self.clearpass, self.md5pass) = \
- passwordutils.get_pw_tuple(self.clearpass)
- if not self.sysuid:
- self.sysuid = self.getnextsysuid()
+ if not self.delegateto.username:
+ self.delegateto.username = self._get_next_sysusername()
+ if not self.delegateto.usertype:
+ self.delegateto.usertype = self._get_default_sysusertype()
+ if not self.delegateto.home:
+ self.delegateto.home = self._get_home(self.delegateto.username)
+ if not self.delegateto.shell:
+ self.delegateto.shell = self._get_default_shell()
+ (self.delegateto.clearpass, self.delegateto.md5pass) = \
+ passwordutils.get_pw_tuple(self.delegateto.clearpass)
+ if not self.delegateto.sysuid:
+ self.delegateto.sysuid = self._get_next_sysuid()
self.validate()
- def getnextsysusername(self):
- prefix = self.config.get('sysuser', 'nameprefix')
+ def _get_next_sysusername(self):
+ prefix = config.get('sysuser', 'nameprefix')
usernames = [user.username for user in \
- getenttools.finduserbyprefix(prefix)]
+ getenttools.find_user_by_prefix(prefix)]
maxid = max([int(username[len(prefix):]) for username in usernames])
for num in range(1, maxid + 1):
username = "%s%02d" % (prefix, num)
if not username in usernames:
return username
- def getdefaultsysusertype(self):
+ def _get_default_sysusertype(self):
return 1
- def gethome(self, sysusername):
+ def _get_home(self, sysusername):
"""Gets a valid home directory for the given user name."""
- return os.path.join(self.config.get('sysuser', 'homedirbase'),
+ return os.path.join(config.get('sysuser', 'homedirbase'),
sysusername)
- def getdefaultshell(self):
+ def _get_default_shell(self):
return False
- def getshellbinary(self):
- if self.shell:
- return self.config.get('sysuser', 'shellyes')
- return self.config.get('sysuser', 'shellno')
+ def _get_shell_binary(self):
+ if self.delegateto.shell:
+ return config.get('sysuser', 'shellyes')
+ return config.get('sysuser', 'shellno')
- def getnextsysuid(self):
- uid = int(self.config.get('sysuser', 'minuid'))
- muid = getenttools.getmaxuid(int(self.config.get('sysuser',
- 'maxuid')))
+ def _get_next_sysuid(self):
+ uid = int(config.get('sysuser', 'minuid'))
+ muid = getenttools.get_max_uid(int(config.get('sysuser', 'maxuid')))
if muid >= uid:
uid = muid + 1
return uid
- def populate_home(self):
- templatedir = self.config.get('sysuser', 'hometemplate')
- cmdline = 'install -d --owner="%(username)s" --group="%(group)s" "%(home)s"' % {
- 'username' : self.username,
- 'group' : self.config.get('sysuser', 'defaultgroup'),
- 'home' : self.home }
- self.sucommand(cmdline)
+ def _populate_home(self):
+ templatedir = get_template_dir(config.get('sysuser', 'hometemplate'))
cmdline = 'cp -R "%(template)s" "%(home)s"' % {
'template' : templatedir,
- 'home' : self.home }
+ 'home' : self.delegateto.home }
self.sucommand(cmdline)
cmdline = 'chown -R "%(username)s":"%(group)s" %(home)s' % {
- 'username' : self.username,
- 'group' : self.config.get('sysuser', 'defaultgroup'),
- 'home' : self.home }
+ 'username' : self.delegateto.username,
+ 'group' : config.get('sysuser', 'defaultgroup'),
+ 'home' : self.delegateto.home }
self.sucommand(cmdline)
- def create_hook(self):
- gecos = self.config.get('sysuser', 'gecos')
- gecos = gecos % (self.username)
+ def _mail_sysuser(self):
+ template = get_template(config.get('common', 'mailtemplates'),
+ config.get('sysuser', 'create.mail'))
+ text = template.substitute({
+ 'uid' : self.delegateto.sysuid,
+ 'firstname' : self.delegateto.client.firstname,
+ 'lastname' : self.delegateto.client.lastname,
+ 'email' : self.delegateto.client.email,
+ 'username' : self.delegateto.username,
+ 'password' : self.delegateto.clearpass,
+ 'home' : self.delegateto.home,
+ 'shell' : self._get_shell_binary()})
+ template = get_template_string(config.get('sysuser', 'create_subject'))
+ subject = template.substitute({
+ 'username' : self.delegateto.username})
+ self.send_mail(subject, text)
+
+ def create_hook(self, session):
+ gecos = config.get('sysuser', 'gecos') % (self.delegateto.username)
cmdline = 'adduser --home "%(home)s" --shell "%(shell)s" --no-create-home --uid %(sysuid)d --ingroup "%(group)s" --disabled-password --gecos "%(gecos)s" %(username)s' % {
- 'home' : self.home,
- 'shell' : self.getshellbinary(),
- 'sysuid' : self.sysuid,
- 'group' : self.config.get('sysuser', 'defaultgroup'),
+ 'home' : self.delegateto.home,
+ 'shell' : self._get_shell_binary(),
+ 'sysuid' : self.delegateto.sysuid,
+ 'group' : config.get('sysuser', 'defaultgroup'),
'gecos' : gecos,
- 'username' : self.username}
+ 'username' : self.delegateto.username}
self.sucommand(cmdline)
cmdline = 'chpasswd --encrypted'
inline = '%(username)s:%(md5pass)s' % {
- 'username' : self.username,
- 'md5pass' : self.md5pass}
+ 'username' : self.delegateto.username,
+ 'md5pass' : self.delegateto.md5pass}
self.sucommand(cmdline, inline)
- self.populate_home()
+ self._populate_home()
+ self._mail_sysuser()
- def delete_hook(self):
- backupdir = os.path.join(self.config.get('common',
- 'backupdir'),
- self.config.get('sysuser',
- 'homebackupdir'))
+ def delete_hook(self, session):
+ if self.delegateto.domains:
+ raise CannotDeleteError(
+ self.delegateto,
+ "it still has the following domains assigned: %s" % (
+ ", ".join([domain.name for domain in \
+ self.delegateto.domains])))
+ backupdir = os.path.join(config.get('common', 'backupdir'),
+ config.get('sysuser', 'homebackupdir'))
if not os.path.isdir(backupdir):
cmdline = 'mkdir -p "%(backupdir)s"' % {
'backupdir' : backupdir}
@@ -141,13 +144,11 @@ class Sysuser(BackendEntity):
raise Exception("could not create backup directory")
cmdline = 'deluser --remove-home --backup --backup-to "%(backupdir)s" %(username)s' % {
'backupdir' : backupdir,
- 'username' : self.username}
+ 'username' : self.delegateto.username}
self.sucommand(cmdline)
-sysusermapper = mapper(Sysuser, sysuser_table)
-
class SysuserHandler(BackendEntityHandler):
"""BackendEntityHandler for Sysuser entities."""
def __init__(self, verbose = False):
- BackendEntityHandler.__init__(self, Sysuser, verbose)
+ BackendEntityHandler.__init__(self, SysuserEntity, Sysuser, verbose)
diff --git a/gnuviechadmin/backend/tables.py b/gnuviechadmin/backend/tables.py
index a79192a..b8d314d 100644
--- a/gnuviechadmin/backend/tables.py
+++ b/gnuviechadmin/backend/tables.py
@@ -24,82 +24,49 @@ import sys
import migrate.versioning.api
from settings import *
-dbversion = migrate.versioning.api.db_version(
- config.get('database', 'uri'),
- config.get('database', 'repository'))
-if dbversion < required_version:
- print("""Database version is %d but required version is %d, run
+try:
+ dbversion = migrate.versioning.api.db_version(
+ config.get('database', 'uri'),
+ config.get('database', 'repository'))
+ if dbversion < required_version:
+ print("""Database version is %d but required version is %d, run
migrate upgrade %s %s
to fix this.""" %
- (dbversion, required_version, config.get('database', 'uri'),
- config.get('database', 'repository')))
+ (dbversion, required_version, config.get('database', 'uri'),
+ config.get('database', 'repository')))
+ sys.exit(1)
+except exceptions.NoSuchTableError, nste:
+ print nste
sys.exit(1)
meta = BoundMetaData(config.get('database', 'uri'))
+#meta.engine.echo = True
client_table = Table(
- 'client', meta,
- Column('clientid', Integer, primary_key=True),
- Column('title', String(10)),
- Column('firstname', String(64), nullable=False),
- Column('lastname', String(64), nullable=False),
- Column('address1', String(64), nullable=False),
- Column('address2', String(64)),
- Column('zip', String(7), nullable=False),
- Column('city', String(64), nullable=False),
- Column('country', String(5), nullable=False),
- Column('phone', String(32), nullable=False),
- Column('mobile', String(32)),
- Column('fax', String(32)),
- Column('email', String(64), unique=True, nullable=False),
- schema = dbschema
- )
+ 'client', meta, schema = dbschema, autoload = True)
sysuser_table = Table(
- 'sysuser', meta,
- Column('sysuserid', Integer, primary_key=True),
- Column('username', String(12), nullable=False, unique=True),
- Column('usertype', Integer, nullable=False, default=0, index=True),
- Column('home', String(128)),
- Column('shell', Boolean, nullable=False, default=False),
- Column('clearpass', String(64)),
- Column('md5pass', String(34)),
- Column('clientid', Integer, ForeignKey("client.clientid"),
- nullable=False),
- Column('sysuid', Integer, nullable=False, unique=True),
- Column('lastchange', DateTime, default=func.now()),
- schema = dbschema
- )
+ 'sysuser', meta, ForeignKeyConstraint(['clientid'], ['client.clientid']),
+ schema = dbschema, autoload = True)
domain_table = Table(
- 'domain', meta,
- Column('domainid', Integer, primary_key=True),
- Column('name', String(255), nullable=False, unique=True),
- Column('master', String(20)),
- Column('last_check', Integer),
- Column('type', String(6), nullable=False),
- Column('notified_serial', Integer),
- Column('sysuserid', Integer, ForeignKey("sysuser.sysuserid"),
- nullable=False),
- schema = dbschema
- )
+ 'domain', meta, ForeignKeyConstraint(['sysuserid'], ['sysuser.sysuserid']),
+ schema = dbschema, autoload = True)
record_table = Table(
- 'record', meta,
- Column('recordid', Integer, primary_key=True),
- Column('domainid', Integer, ForeignKey("domain.domainid"),
- nullable=False),
- Column('name', String(255)),
- Column('type', String(6)),
- Column('content', String(255)),
- Column('ttl', Integer),
- Column('prio', Integer),
- Column('change_date', Integer),
- schema = dbschema
- )
+ 'record', meta, ForeignKeyConstraint(['domainid'], ['domain.domainid']),
+ schema = dbschema, autoload = True)
supermaster_table = Table(
'supermaster', meta,
- Column('ip', String(25), nullable=False),
- Column('nameserver', String(255), nullable=False),
- Column('account', Integer, ForeignKey("sysuser.sysuserid"),
- nullable=False),
- schema = dbschema
- )
+ ForeignKeyConstraint(['account'], ['sysuser.sysuserid']),
+ schema = dbschema, autoload = True)
+mailaccount_table = Table(
+ 'mailaccount', meta,
+ ForeignKeyConstraint(['domainid'], ['domain.domainid']),
+ schema = dbschema, autoload = True)
+mailaddress_table = Table(
+ 'mailaddress', meta,
+ ForeignKeyConstraint(['domainid'], ['domain.domainid']),
+ schema = dbschema, autoload = True)
+mailtarget_table = Table(
+ 'mailtarget', meta,
+ ForeignKeyConstraint(['mailaddressid'], ['mailaddress.mailaddressid']),
+ schema = dbschema, autoload = True)
diff --git a/gnuviechadmin/cli/CliCommand.py b/gnuviechadmin/cli/CliCommand.py
index fcc3646..7cabc29 100644
--- a/gnuviechadmin/cli/CliCommand.py
+++ b/gnuviechadmin/cli/CliCommand.py
@@ -19,7 +19,7 @@
#
# Version: $Id$
-import getopt, sys
+import getopt, sys, logging
from gnuviechadmin.exceptions import GnuviechadminError
class CliCommand:
@@ -198,6 +198,8 @@ Common options:
def __init__(self, args):
"""This initializes the command with the given command line
arguments and executes it."""
+ self.logger = logging.getLogger("%s.%s" % (
+ self.__class__.__module__, self.__class__.__name__))
self._data = {}
if len(args) > 0:
if args[0] in self._subcommands():
diff --git a/gnuviechadmin/cli/client.py b/gnuviechadmin/cli/client.py
index ec26ff7..0c46261 100644
--- a/gnuviechadmin/cli/client.py
+++ b/gnuviechadmin/cli/client.py
@@ -59,6 +59,8 @@ class ClientCli(CliCommand.CliCommand):
"the client id", True)])}
def _execute(self, subcommand):
+ self.logger.debug("execute %s with data %s", subcommand,
+ str(self._data))
from gnuviechadmin.backend import client
from gnuviechadmin import exceptions
if subcommand == "create":
diff --git a/gnuviechadmin/cli/domain.py b/gnuviechadmin/cli/domain.py
index da823f5..40e7888 100644
--- a/gnuviechadmin/cli/domain.py
+++ b/gnuviechadmin/cli/domain.py
@@ -43,6 +43,8 @@ class DomainCli(CliCommand.CliCommand):
"the domain id", True)])}
def _execute(self, subcommand):
+ self.logger.debug("execute %s with data %s", subcommand,
+ str(self._data))
from gnuviechadmin.backend.domain import DomainHandler
from gnuviechadmin import exceptions
if subcommand == "create":
diff --git a/gnuviechadmin/cli/record.py b/gnuviechadmin/cli/record.py
index efe8a99..3b07c12 100644
--- a/gnuviechadmin/cli/record.py
+++ b/gnuviechadmin/cli/record.py
@@ -37,16 +37,19 @@ class RecordCli(CliCommand.CliCommand):
(["-p", "--prio"], "prio",
"MX record priority", False),
(["--ttl"], "ttl",
- "Time to live", False),
+ "time to live", False),
(["-d", "--domainid"], "domainid",
- "Domain id", True)]),
+ "domain id", True)]),
'list' : ("lists existing records",
- []),
+ [(["-d", "--domainid"], "domainid",
+ "domain id", False)]),
'delete' : ("delete a record",
[(["-r", "--recordid"], "recordid",
"the record id", True)])}
def _execute(self, subcommand):
+ self.logger.debug("execute %s with data %s", subcommand,
+ str(self._data))
from gnuviechadmin.backend.record import RecordHandler
from gnuviechadmin import exceptions
if subcommand == "create":
@@ -60,7 +63,7 @@ class RecordCli(CliCommand.CliCommand):
print cfe
sys.exit(2)
elif subcommand == "list":
- records = RecordHandler(self._verbose).fetchall()
+ records = RecordHandler(self._verbose).fetchall(**self._data)
for record in records:
print record
elif subcommand == "delete":
diff --git a/gnuviechadmin/cli/sysuser.py b/gnuviechadmin/cli/sysuser.py
index 27ac177..88719cd 100644
--- a/gnuviechadmin/cli/sysuser.py
+++ b/gnuviechadmin/cli/sysuser.py
@@ -47,6 +47,8 @@ class SysuserCli(CliCommand.CliCommand):
"the system user id", True)])}
def _execute(self, subcommand):
+ self.logger.debug("execute %s with data %s", subcommand,
+ str(self._data))
from gnuviechadmin.backend import sysuser
from gnuviechadmin import exceptions
if subcommand == "create":
diff --git a/gnuviechadmin/defaults.cfg b/gnuviechadmin/defaults.cfg
index a065003..2105156 100644
--- a/gnuviechadmin/defaults.cfg
+++ b/gnuviechadmin/defaults.cfg
@@ -35,6 +35,13 @@ repository = /etc/gnuviechadmin/dbrepo
[common]
suwrapper = sudo
backupdir = /var/backups/gnuviechadmin
+templatedir = /etc/gnuviechadmin/templates
+mailtemplates = mails
+log.cfg = /etc/gnuviechadmin/logging.cfg
+
+[client]
+create.mail = create_client.txt
+create_subject = A new client ${firstname} ${lastname} has been created.
[sysuser]
nameprefix = usr
@@ -46,4 +53,27 @@ shellno = /usr/bin/scponly
defaultgroup = wwwusers
gecos = Webuser %s
homebackupdir = homes
-hometemplate = /etc/gnuviechadmin/templates/home
+hometemplate = home
+create.mail = create_sysuser.txt
+create_subject = A new system user ${username} has been created.
+
+[domain]
+defaultmxprio = 5
+defaultrefresh = 86400
+defaultretry = 7200
+defaultexpire = 1209600
+defaultminimumttl = 86400
+defaultttl = 86400
+htdir = html
+logpath = /var/log/apache2
+statspath = /home/stats
+htdocstemplate = domain
+conftemplates = domainconf
+apachetemplate = vhost.conf
+statshtaccesstemplate = htaccess-stats
+modlogantemplate = modlogan.conf
+modlogandir = /var/lib/gnuviechadmin/stats
+sitesdir = /etc/apache2/sites-available
+authdir = /etc/apache2/authdata
+create.mail = create_domain.txt
+create_subject = A new domain ${domain} has been created.
diff --git a/gnuviechadmin/exceptions.py b/gnuviechadmin/exceptions.py
index 10a0ff7..ec0d290 100644
--- a/gnuviechadmin/exceptions.py
+++ b/gnuviechadmin/exceptions.py
@@ -73,3 +73,16 @@ class ValidationFailedError(GnuviechadminError):
if self.cause:
msg += " The reason is %s." % (str(self.cause))
return msg
+
+class CannotDeleteError(GnuviechadminError):
+ """This exception should be raised if an entity cannot be deleted
+ because of some unmatched precondition."""
+ def __init__(self, instance, cause = None):
+ self.instance = instance
+ self.cause = cause
+
+ def __str__(self):
+ msg = "Cannot delete %s." % (str(self.instance))
+ if self.cause:
+ msg += " The reason is %s." % (str(self.cause))
+ return msg
diff --git a/gnuviechadmin/logging.cfg b/gnuviechadmin/logging.cfg
new file mode 100644
index 0000000..a0cdfa7
--- /dev/null
+++ b/gnuviechadmin/logging.cfg
@@ -0,0 +1,34 @@
+[formatters]
+keys=simple
+
+[formatter_simple]
+format=%(asctime)s %(levelname)s %(name)s: %(message)s
+datefmt=
+
+[handlers]
+keys=handler01,handler02
+
+[handler_handler01]
+class=handlers.RotatingFileHandler
+level=DEBUG
+formatter=simple
+args=('gnuviechadmin.log', 'a', 10485760, 5)
+
+[handler_handler02]
+class=StreamHandler
+level=NOTSET
+formatter=simple
+args=(sys.stdout,)
+
+[loggers]
+keys=root,gnuviechadmin
+
+[logger_root]
+level=NOTSET
+handlers=handler02
+
+[logger_gnuviechadmin]
+level=DEBUG
+handlers=handler01
+propagate=0
+qualname=gnuviechadmin
\ No newline at end of file
diff --git a/gnuviechadmin/util/getenttools.py b/gnuviechadmin/util/getenttools.py
index be3ed1f..ffa068e 100644
--- a/gnuviechadmin/util/getenttools.py
+++ b/gnuviechadmin/util/getenttools.py
@@ -19,7 +19,7 @@
#
# Version: $Id$
-import os, popen2
+import pwd, grp
class PasswdUser(object):
"""This class represents users in the user database."""
@@ -45,7 +45,7 @@ class PasswdGroup(object):
def __init__(self, groupname, pw, gid, members):
self.groupname = groupname
self.gid = int(gid)
- self.members = members.split(",")
+ self.members = members
def __repr__(self):
return "%s(%s:%d:%s)" % (self.__class__.__name__,
@@ -53,42 +53,41 @@ class PasswdGroup(object):
self.gid,
",".join(self.members))
-def parsegroups():
- (stdout, stdin) = popen2.popen2("getent group")
- return [PasswdGroup(*arr) for arr in [line.strip().split(":") for line in stdout]]
+def parse_groups():
+ return [PasswdGroup(*arr) for arr in grp.getgrall()]
-def parseusers():
- (stdout, stdin) = popen2.popen2("getent passwd")
- return [PasswdUser(*arr) for arr in [line.strip().split(":") for line in stdout]]
+def parse_users():
+ return [PasswdUser(*arr) for arr in pwd.getpwall()]
-def finduserbyprefix(prefix):
+def find_user_by_prefix(prefix):
"""Finds all user entries with the given prefix."""
- return [user for user in parseusers() if user.username.startswith(prefix)]
+ return [user for user in parse_users() if user.username.startswith(prefix)]
-def getuserbyid(uid):
+def get_user_by_id(uid):
"""Gets the user with the given user id."""
- users = [user for user in parseusers() if user.uid == uid]
+ users = [user for user in parse_users() if user.uid == uid]
if users:
return users[0]
return None
-def getgroupbyid(gid):
+def get_group_by_id(gid):
"""Gets the group with the given group id."""
- groups = [group for group in parsegroups() if group.gid == gid]
+ groups = [group for group in parse_groups() if group.gid == gid]
if groups:
return groups[0]
return None
-def getmaxuid(boundary = 65536):
+def get_max_uid(boundary = 65536):
"""Gets the highest uid value."""
- return max([user.uid for user in parseusers() if user.uid <= boundary])
+ return max([user.uid for user in parse_users() if user.uid <= boundary])
-def getmaxgid(boundary = 65536):
+def get_max_gid(boundary = 65536):
"""Gets the highest gid value."""
- return max([group.gid for group in parsegroups() if group.gid <= boundary])
+ return max([group.gid for group in parse_groups() \
+ if group.gid <= boundary])
if __name__ == "__main__":
- print "Max UID is %d" % (getmaxuid(40000))
- print "Max GID is %d" % (getmaxgid(40000))
- print "User with max UID is %s" % (getuserbyid(getmaxuid(40000)))
- print "Group with max GID is %s" % (getgroupbyid(getmaxgid(40000)))
+ print "Max UID is %d" % (get_max_uid(40000))
+ print "Max GID is %d" % (get_max_gid(40000))
+ print "User with max UID is %s" % (get_user_by_id(get_max_uid(40000)))
+ print "Group with max GID is %s" % (get_group_by_id(get_max_gid(40000)))
diff --git a/gnuviechadmin/util/passwordutils.py b/gnuviechadmin/util/passwordutils.py
index 111e2c6..64211df 100644
--- a/gnuviechadmin/util/passwordutils.py
+++ b/gnuviechadmin/util/passwordutils.py
@@ -21,16 +21,17 @@
import crypt, crack, random
+_pwchars = []
+for pair in (('0', '9'), ('A', 'Z'), ('a', 'z')):
+ _pwchars.extend(range(ord(pair[0]), ord(pair[1])))
+for char in "-+/*_@":
+ _pwchars.append(ord(char))
+
def generatepassword(minlength = 8, maxlength = 12):
"""Generates a random password with a length between the given
minlength and maxlength values."""
- pwchars = []
- for pair in (('0', '9'), ('A', 'Z'), ('a', 'z')):
- pwchars.extend(range(ord(pair[0]), ord(pair[1])))
- for char in "-+/*_@":
- pwchars.append(ord(char))
return "".join([chr(letter) for letter in \
- random.sample(pwchars,
+ random.sample(_pwchars,
random.randint(minlength, maxlength))])
def checkpassword(password):
@@ -45,7 +46,7 @@ def checkpassword(password):
def md5_crypt_password(password):
"""Hashes the given password with MD5 and a random salt value."""
salt = "".join([chr(letter) for letter in \
- random.sample(range(ord('a'), ord('z')), 8)])
+ random.sample(_pwchars, 8)])
return crypt.crypt(password, '$1$' + salt)
def get_pw_tuple(password = None):