Browse Source

- mail related tables in database schema

- gpg encryption for mails
- domain creation and deletion completed
- logging
- use pwd and grp


git-svn-id: file:///home/www/usr01/svn/gnuviechadmin/gnuviech.info/gnuviechadmin/trunk@230 a67ec6bc-e5d5-0310-a910-815c51eb3124
master
Jan Dittberner 14 years ago
parent
commit
fdea3217c8
  1. 7
      bin/gva
  2. 49
      data/dbrepo/versions/3/3.py
  3. 6
      data/templates/domain/index.html
  4. 4
      data/templates/domainconf/htaccess-stats
  5. 42
      data/templates/domainconf/modlogan.conf
  6. 10
      data/templates/domainconf/vhost.conf
  7. 8
      data/templates/mails/create_client.txt
  8. 14
      data/templates/mails/create_domain.txt
  9. 8
      data/templates/mails/create_sysuser.txt
  10. 101
      gnuviechadmin/backend/BackendEntity.py
  11. 75
      gnuviechadmin/backend/BackendEntityHandler.py
  12. 73
      gnuviechadmin/backend/BackendTo.py
  13. 60
      gnuviechadmin/backend/client.py
  14. 281
      gnuviechadmin/backend/domain.py
  15. 35
      gnuviechadmin/backend/record.py
  16. 19
      gnuviechadmin/backend/settings.py
  17. 153
      gnuviechadmin/backend/sysuser.py
  18. 99
      gnuviechadmin/backend/tables.py
  19. 4
      gnuviechadmin/cli/CliCommand.py
  20. 2
      gnuviechadmin/cli/client.py
  21. 2
      gnuviechadmin/cli/domain.py
  22. 11
      gnuviechadmin/cli/record.py
  23. 2
      gnuviechadmin/cli/sysuser.py
  24. 32
      gnuviechadmin/defaults.cfg
  25. 13
      gnuviechadmin/exceptions.py
  26. 34
      gnuviechadmin/logging.cfg
  27. 43
      gnuviechadmin/util/getenttools.py
  28. 15
      gnuviechadmin/util/passwordutils.py

7
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,

49
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()

6
data/templates/domain/index.html

@ -0,0 +1,6 @@
<html>
<head><title>Hier entsteht ein neuer Internetauftritt</title></head>
<body>
<p>Hier entsteht der Internetauftritt für www.${domain}.</p>
</body>
</html>

4
data/templates/domainconf/htaccess-stats

@ -0,0 +1,4 @@
AuthType Basic
AuthName "Statistics for ${domain}"
AuthUserFile "${userfile}"
require valid-user

42
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 = -

10
data/templates/domainconf/vhost.conf

@ -0,0 +1,10 @@
<VirtualHost ${ipaddr}:80>
ServerName www.${domain}
ServerAlias ${domain}
Alias /stats ${statsdir}
DocumentRoot ${docroot}
ErrorLog ${logdir}/error.log
CustomLog ${logdir}/access.log combined
</VirtualHost>

8
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}

14
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/

8
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}

101
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()

75
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

73
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'))

60
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 create_hook(self, session):
"""Actions to perform when a client is created."""
self._client_mail()
def delete_hook(self):
pass
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])))
client_mapper = mapper(Client, client_table)
client_mapper.add_property("sysusers", relation(sysuser.Sysuser))
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)

281
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
from gnuviechadmin.exceptions import *
import datetime, os
from record import Record
import datetime
from BackendEntity import *
from BackendEntityHandler import *
from settings import config
from gnuviechadmin.exceptions import *
from settings import *
from BackendTo import Record, Domain
from BackendEntity import BackendEntity
from BackendEntityHandler import BackendEntityHandler
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_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)
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 delete_hook(self):
pass
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)
domain_mapper = mapper(Domain, domain_table)
domain_mapper.add_property("records", relation(Record))
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)

35
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)

19
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)

153
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()
def delete_hook(self):
backupdir = os.path.join(self.config.get('common',
'backupdir'),
self.config.get('sysuser',
'homebackupdir'))
self._populate_home()
self._mail_sysuser()
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)

99
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)

4
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():

2
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":

2
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":

11
gnuviechadmin/cli/record.py

@ -37,16 +37,19 @@ class RecordCli(CliCommand.CliCommand):
(["-p", "--prio"], "prio",