1
0
Fork 0

- 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
This commit is contained in:
Jan Dittberner 2007-07-09 06:46:36 +00:00
parent 3f4457bdca
commit fdea3217c8
28 changed files with 877 additions and 323 deletions

View File

@ -24,7 +24,12 @@ import gnuviechadmin.cli.client
import gnuviechadmin.cli.sysuser import gnuviechadmin.cli.sysuser
import gnuviechadmin.cli.domain import gnuviechadmin.cli.domain
import gnuviechadmin.cli.record 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, commands = [gnuviechadmin.cli.client.ClientCli,
gnuviechadmin.cli.sysuser.SysuserCli, gnuviechadmin.cli.sysuser.SysuserCli,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,41 +19,35 @@
# #
# Version: $Id$ # Version: $Id$
import ConfigParser, os import smtplib, os, logging, tempfile
from subprocess import * from email.MIMEText import MIMEText
from sqlalchemy import * from pyme import core
from pyme.constants.sig import mode
from settings import config
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from subprocess import *
import sqlalchemy
class BackendEntity(object): class BackendEntity(object):
"""This is the abstract base class for all backend entity classes.""" """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.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): def __repr__(self):
if self.verbose: return self.delegateto.__repr__(verbose = 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
def sucommand(self, cmdline, pipedata = None): def sucommand(self, cmdline, pipedata = None):
"""Executes a command as root using the configured suwrapper """Executes a command as root using the configured suwrapper
command. If a pipe is specified it is used as stdin of the command. If a pipe is specified it is used as stdin of the
subprocess.""" 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) toexec = "%s %s" % (suwrapper, cmdline)
if pipedata: if pipedata:
p = Popen(toexec, shell = True, stdin=PIPE) p = Popen(toexec, shell = True, stdin=PIPE)
@ -63,21 +57,80 @@ class BackendEntity(object):
sts = os.waitpid(p.pid, 0) sts = os.waitpid(p.pid, 0)
if self.verbose: if self.verbose:
print "%s|%s: %d" % (pipedata, toexec, sts[1]) print "%s|%s: %d" % (pipedata, toexec, sts[1])
self.logger.info("%s|%s: %d", pipedata, toexec, sts[1])
else: else:
p = Popen(toexec, shell = True) p = Popen(toexec, shell = True)
sts = os.waitpid(p.pid, 0) sts = os.waitpid(p.pid, 0)
if self.verbose: if self.verbose:
print "%s: %s" % (toexec, sts[1]) print "%s: %s" % (toexec, sts[1])
self.logger.info("%s: %s", toexec, sts[1])
return 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): def validate(self):
"""Validates whether all mandatory fields of the entity have """Validates whether all mandatory fields of the entity have
values.""" values."""
missingfields = [] missingfields = []
for key in [col.name for col in \ 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 not col.primary_key and not col.nullable]:
if self.__getattribute__(key) is None: if self.delegateto.__getattribute__(key) is None:
missingfields.append(key) missingfields.append(key)
if missingfields: if missingfields:
raise MissingFieldsError(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()

View File

@ -19,51 +19,68 @@
# #
# Version: $Id$ # Version: $Id$
from sqlalchemy import * import sqlalchemy, logging
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from BackendEntity import * from BackendEntity import *
class BackendEntityHandler(object): 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.entityclass = entityclass
self.toclass = toclass
self.verbose = verbose self.verbose = verbose
def create(self, **kwargs): def create(self, **kwargs):
# try: """Create a new entity of the managed type with the fields set
sess = create_session() to the values in kwargs."""
entity = self.entityclass(self.verbose, **kwargs) self.logger.debug("create with params %s", str(kwargs))
try: sess = sqlalchemy.create_session()
entity.create_hook() transaction = sess.create_transaction()
sess.save(entity) delegate = self.toclass(**kwargs)
sess.flush() entity = self.entityclass(delegate, self.verbose)
except: try:
sess.delete(entity) sess.save(delegate)
sess.flush() sess.flush()
raise sess.refresh(delegate)
# except Exception, e: entity.create_hook(sess)
# raise CreationFailedError(self.entityclass.__name__, e) 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.""" """Fetches all entities of the managed entity type."""
session = create_session() self.logger.debug("fetchall with params %s", str(kwargs))
query = session.query(self.entityclass) session = sqlalchemy.create_session()
allentities = query.select() query = session.query(self.toclass)
for entity in allentities: if kwargs:
BackendEntity.__init__(entity, self.verbose) allentities = query.select_by(**kwargs)
return allentities else:
allentities = query.select()
return [self.entityclass(entity, self.verbose) \
for entity in allentities]
def delete(self, pkvalue): def delete(self, pkvalue):
"""Deletes the entity of the managed entity type that has the """Deletes the entity of the managed entity type that has the
specified primary key value.""" specified primary key value."""
self.logger.debug("delete with primary key %s", str(pkvalue))
sess = sqlalchemy.create_session()
transaction = sess.create_transaction()
try: try:
sess = create_session() to = sess.query(self.toclass).get(pkvalue)
entity = sess.query(self.entityclass).get(pkvalue) if to:
if entity: entity = self.entityclass(to, self.verbose)
BackendEntity.__init__(entity, self.verbose) self.logger.info("delete %s", str(entity))
if self.verbose: if self.verbose:
print "delete %s" % (str(entity)) print "delete %s" % (str(entity))
entity.delete_hook() entity.delete_hook(sess)
sess.delete(entity) sess.delete(to)
sess.flush() sess.flush()
transaction.commit()
except Exception, e: except Exception, e:
raise DeleteFailedError(self.entityclass.__name__, e) transaction.rollback()
self.logger.exception("Exception in delete.")
raise

View File

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

View File

@ -19,42 +19,58 @@
# #
# Version: $Id$ # Version: $Id$
from sqlalchemy import *
from tables import client_table
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from settings import config
import sysuser from BackendTo import *
from BackendEntity import * from BackendEntity import *
from BackendEntityHandler import * from BackendEntityHandler import *
class Client(BackendEntity): class ClientEntity(BackendEntity):
"""Entity class for clients.""" """Entity class for clients."""
_shortkeys = ('clientid', 'firstname', 'lastname', 'email') def __init__(self, delegate, verbose = False, **kwargs):
BackendEntity.__init__(self, delegate, verbose)
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
for (key, value) in kwargs.items(): for (key, value) in kwargs.items():
self.__setattr__(key, value) self.__setattr__(key, value)
if not self.delegateto.country:
self.delegateto.country = self._get_default_country()
self.validate() self.validate()
def create_hook(self): def _client_mail(self):
pass 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): def create_hook(self, session):
pass """Actions to perform when a client is created."""
self._client_mail()
client_mapper = mapper(Client, client_table) def delete_hook(self, session):
client_mapper.add_property("sysusers", relation(sysuser.Sysuser)) """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): class ClientHandler(BackendEntityHandler):
"""BackendEntityHandler for Client entities.""" """BackendEntityHandler for Client entities."""
def __init__(self, verbose = False): def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Client, verbose) BackendEntityHandler.__init__(self, ClientEntity, Client, verbose)

View File

@ -19,37 +19,29 @@
# #
# Version: $Id: client.py 1101 2007-02-28 21:15:20Z jan $ # Version: $Id: client.py 1101 2007-02-28 21:15:20Z jan $
from sqlalchemy import * import datetime, os
from tables import domain_table
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from settings import *
from BackendTo import Record, Domain
from BackendEntity import BackendEntity
from BackendEntityHandler import BackendEntityHandler
from record import Record class DomainEntity(BackendEntity):
import datetime
from BackendEntity import *
from BackendEntityHandler import *
from settings import config
class Domain(BackendEntity):
"""Entity class for DNS domains.""" """Entity class for DNS domains."""
_shortkeys = ("domainid", "sysuserid", "name", "type")
_valid_domain_types = ("MASTER", "SLAVE") _valid_domain_types = ("MASTER", "SLAVE")
def __init__(self, verbose = False, **kwargs): def __init__(self, delegate, verbose = False, **kwargs):
BackendEntity.__init__(self, verbose) BackendEntity.__init__(self, delegate, verbose)
self.domainid = None
self.sysuserid = None
self.name = None
self.type = None
self.master = None
self.ns1 = None self.ns1 = None
self.ns2 = None self.ns2 = None
self.mx = None self.mx = None
self.ipaddr = None self.ipaddr = None
for (key, value) in kwargs.items(): for (key, value) in kwargs.items():
self.__setattr__(key, value) self.__setattr__(key, value)
if not self.type: if not self.delegateto.type:
self.type = self.getdefaultdomaintype() self.delegateto.type = self.getdefaultdomaintype()
if not self.ns1: if not self.ns1:
self.ns1 = config.get('domain', 'defaultns1') self.ns1 = config.get('domain', 'defaultns1')
if not self.ns2: if not self.ns2:
@ -58,7 +50,7 @@ class Domain(BackendEntity):
self.mx = config.get('domain', 'defaultmx') self.mx = config.get('domain', 'defaultmx')
if not self.ipaddr: if not self.ipaddr:
self.ipaddr = config.get('domain', 'defaultip') self.ipaddr = config.get('domain', 'defaultip')
self.type = self.type.upper() self.delegateto.type = self.delegateto.type.upper()
self.validate() self.validate()
def getdefaultdomaintype(self): def getdefaultdomaintype(self):
@ -66,13 +58,13 @@ class Domain(BackendEntity):
def validate(self): def validate(self):
BackendEntity.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( raise ValidationFailedError(
self, "invalid domain type %s" % (self.type)) self, "invalid domain type %s" % (self.delegateto.type))
if self.type == 'SLAVE' and not self.master: if self.delegateto.type == 'SLAVE' and not self.delegateto.master:
raise ValidationFailedError( raise ValidationFailedError(
self, "you have to specify a master for slave domains.") 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: if not self.ns1 or not self.ns2:
raise ValidationFailedError( raise ValidationFailedError(
self, "two nameservers must be specified.") self, "two nameservers must be specified.")
@ -80,10 +72,17 @@ class Domain(BackendEntity):
raise ValidationFailedError( raise ValidationFailedError(
self, "a primary mx host must be specified.") self, "a primary mx host must be specified.")
def _getnewserial(self): def _getnewserial(self, oldserial = None):
current = datetime.datetime.now() current = datetime.datetime.now()
return int("%04d%02d%02d01" % \ datepart = "%04d%02d%02d" % \
(current.year, current.month, current.day)) (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): def _getnewsoa(self):
return '%s %s %d %d %d %d %d' % \ return '%s %s %d %d %d %d %d' % \
@ -95,36 +94,228 @@ class Domain(BackendEntity):
config.getint('domain', 'defaultexpire'), config.getint('domain', 'defaultexpire'),
config.getint('domain', 'defaultminimumttl')) config.getint('domain', 'defaultminimumttl'))
def create_hook(self): def update_serial(self, session):
self.records.append(Record( query = session.query(Record)
name = self.name, type = 'SOA', 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(), content = self._getnewsoa(),
ttl = config.getint('domain', 'defaultttl'))) ttl = config.getint('domain', 'defaultttl')))
self.records.append(Record( self.delegateto.records.append(Record(
name = self.name, type = 'NS', content = self.ns1, name = self.delegateto.name, type = 'NS', content = self.ns1,
ttl = config.getint('domain', 'defaultttl'))) ttl = config.getint('domain', 'defaultttl')))
self.records.append(Record( self.delegateto.records.append(Record(
name = self.name, type = 'NS', content = self.ns2, name = self.delegateto.name, type = 'NS', content = self.ns2,
ttl = config.getint('domain', 'defaultttl'))) ttl = config.getint('domain', 'defaultttl')))
self.records.append(Record( self.delegateto.records.append(Record(
name = self.name, type = 'MX', content = self.mx, name = self.delegateto.name, type = 'MX', content = self.mx,
ttl = config.getint('domain', 'defaultttl'), ttl = config.getint('domain', 'defaultttl'),
prio = config.getint('domain', 'defaultmxprio'))) prio = config.getint('domain', 'defaultmxprio')))
self.records.append(Record( self.delegateto.records.append(Record(
name = self.name, type = 'A', content = self.ipaddr, name = self.delegateto.name, type = 'A', content = self.ipaddr,
ttl = config.getint('domain', 'defaultttl'))) ttl = config.getint('domain', 'defaultttl')))
self.records.append(Record( self.delegateto.records.append(Record(
name = "www.%s" % (self.name), type = 'A', content = self.ipaddr, name = "www.%s" % (self.delegateto.name), type = 'A',
content = self.ipaddr,
ttl = config.getint('domain', 'defaultttl'))) 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): def _delete_apache_conf(self):
pass 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) def _delete_stats_conf(self):
domain_mapper.add_property("records", relation(Record)) 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): class DomainHandler(BackendEntityHandler):
"""BackendEntityHandler for Domain entities.""" """BackendEntityHandler for Domain entities."""
def __init__(self, verbose = False): def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Domain, verbose) BackendEntityHandler.__init__(self, DomainEntity, Domain, verbose)

View File

@ -19,37 +19,24 @@
# #
# Version: $Id$ # Version: $Id$
from sqlalchemy import *
from tables import record_table
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from BackendTo import Record, Domain
from domain import DomainEntity
from BackendEntity import * from BackendEntity import *
from BackendEntityHandler import * from BackendEntityHandler import *
class Record(object): class RecordEntity(BackendEntity):
"""Entity class for DNS domain records.""" """Entity class for DNS domain records."""
def __init__(self, **kwargs): def create_hook(self, session):
for (key, value) in kwargs.items(): domain = session.load(Domain, self.delegateto.domainid)
self.__setattr__(key, value) DomainEntity(domain).update_serial(session)
#_shortkeys = ("recordid", "domainid", "name", "type", "content") def delete_hook(self, session):
domain = session.load(Domain, self.delegateto.domainid)
#def __init__(self, verbose = False, **kwargs): DomainEntity(domain).update_serial(session)
# 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)
class RecordHandler(BackendEntityHandler): class RecordHandler(BackendEntityHandler):
"""BackendEntityHandler for Record entities.""" """BackendEntityHandler for Record entities."""
def __init__(self, verbose = False): def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Record, verbose) BackendEntityHandler.__init__(self, RecordEntity, Record, verbose)

View File

@ -19,13 +19,28 @@
# #
# Version: $Id$ # Version: $Id$
import ConfigParser, os import ConfigParser, os, string, logging.config
# global settings which must not be user configurable # global settings which must not be user configurable
required_version = 2 required_version = 3
dbschema = 'gva' dbschema = 'gva'
# load user configuration # load user configuration
config = ConfigParser.ConfigParser() config = ConfigParser.ConfigParser()
config.readfp(open('gnuviechadmin/defaults.cfg')) config.readfp(open('gnuviechadmin/defaults.cfg'))
config.read(['gnuviechadmin/gva.cfg', os.path.expanduser('~/.gva.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)

View File

@ -19,120 +19,123 @@
# #
# Version: $Id$ # Version: $Id$
from sqlalchemy import *
from tables import sysuser_table
from gnuviechadmin.exceptions import * from gnuviechadmin.exceptions import *
from gnuviechadmin.util import passwordutils, getenttools from gnuviechadmin.util import passwordutils, getenttools
from settings import config
from BackendTo import *
from BackendEntity import * from BackendEntity import *
from BackendEntityHandler import * from BackendEntityHandler import *
class Sysuser(BackendEntity): class SysuserEntity(BackendEntity):
"""Entity class for system users.""" """Entity class for system users."""
_shortkeys = ("sysuserid", "clientid", "username", "home", "shell") def __init__(self, delegate, verbose = False, **kwargs):
BackendEntity.__init__(self, delegate, verbose)
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
for (key, value) in kwargs.items(): for (key, value) in kwargs.items():
self.__setattr__(key, value) self.__setattr__(key, value)
if not self.username: if not self.delegateto.username:
self.username = self.getnextsysusername() self.delegateto.username = self._get_next_sysusername()
if not self.usertype: if not self.delegateto.usertype:
self.usertype = self.getdefaultsysusertype() self.delegateto.usertype = self._get_default_sysusertype()
if not self.home: if not self.delegateto.home:
self.home = self.gethome(self.username) self.delegateto.home = self._get_home(self.delegateto.username)
if not self.shell: if not self.delegateto.shell:
self.shell = self.getdefaultshell() self.delegateto.shell = self._get_default_shell()
(self.clearpass, self.md5pass) = \ (self.delegateto.clearpass, self.delegateto.md5pass) = \
passwordutils.get_pw_tuple(self.clearpass) passwordutils.get_pw_tuple(self.delegateto.clearpass)
if not self.sysuid: if not self.delegateto.sysuid:
self.sysuid = self.getnextsysuid() self.delegateto.sysuid = self._get_next_sysuid()
self.validate() self.validate()
def getnextsysusername(self): def _get_next_sysusername(self):
prefix = self.config.get('sysuser', 'nameprefix') prefix = config.get('sysuser', 'nameprefix')
usernames = [user.username for user in \ 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]) maxid = max([int(username[len(prefix):]) for username in usernames])
for num in range(1, maxid + 1): for num in range(1, maxid + 1):
username = "%s%02d" % (prefix, num) username = "%s%02d" % (prefix, num)
if not username in usernames: if not username in usernames:
return username return username
def getdefaultsysusertype(self): def _get_default_sysusertype(self):
return 1 return 1
def gethome(self, sysusername): def _get_home(self, sysusername):
"""Gets a valid home directory for the given user name.""" """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) sysusername)
def getdefaultshell(self): def _get_default_shell(self):
return False return False
def getshellbinary(self): def _get_shell_binary(self):
if self.shell: if self.delegateto.shell:
return self.config.get('sysuser', 'shellyes') return config.get('sysuser', 'shellyes')
return self.config.get('sysuser', 'shellno') return config.get('sysuser', 'shellno')
def getnextsysuid(self): def _get_next_sysuid(self):
uid = int(self.config.get('sysuser', 'minuid')) uid = int(config.get('sysuser', 'minuid'))
muid = getenttools.getmaxuid(int(self.config.get('sysuser', muid = getenttools.get_max_uid(int(config.get('sysuser', 'maxuid')))
'maxuid')))
if muid >= uid: if muid >= uid:
uid = muid + 1 uid = muid + 1
return uid return uid
def populate_home(self): def _populate_home(self):
templatedir = self.config.get('sysuser', 'hometemplate') templatedir = get_template_dir(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)
cmdline = 'cp -R "%(template)s" "%(home)s"' % { cmdline = 'cp -R "%(template)s" "%(home)s"' % {
'template' : templatedir, 'template' : templatedir,
'home' : self.home } 'home' : self.delegateto.home }
self.sucommand(cmdline) self.sucommand(cmdline)
cmdline = 'chown -R "%(username)s":"%(group)s" %(home)s' % { cmdline = 'chown -R "%(username)s":"%(group)s" %(home)s' % {
'username' : self.username, 'username' : self.delegateto.username,
'group' : self.config.get('sysuser', 'defaultgroup'), 'group' : config.get('sysuser', 'defaultgroup'),
'home' : self.home } 'home' : self.delegateto.home }
self.sucommand(cmdline) self.sucommand(cmdline)
def create_hook(self): def _mail_sysuser(self):
gecos = self.config.get('sysuser', 'gecos') template = get_template(config.get('common', 'mailtemplates'),
gecos = gecos % (self.username) 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' % { 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, 'home' : self.delegateto.home,
'shell' : self.getshellbinary(), 'shell' : self._get_shell_binary(),
'sysuid' : self.sysuid, 'sysuid' : self.delegateto.sysuid,
'group' : self.config.get('sysuser', 'defaultgroup'), 'group' : config.get('sysuser', 'defaultgroup'),
'gecos' : gecos, 'gecos' : gecos,
'username' : self.username} 'username' : self.delegateto.username}
self.sucommand(cmdline) self.sucommand(cmdline)
cmdline = 'chpasswd --encrypted' cmdline = 'chpasswd --encrypted'
inline = '%(username)s:%(md5pass)s' % { inline = '%(username)s:%(md5pass)s' % {
'username' : self.username, 'username' : self.delegateto.username,
'md5pass' : self.md5pass} 'md5pass' : self.delegateto.md5pass}
self.sucommand(cmdline, inline) self.sucommand(cmdline, inline)
self.populate_home() self._populate_home()
self._mail_sysuser()
def delete_hook(self): def delete_hook(self, session):
backupdir = os.path.join(self.config.get('common', if self.delegateto.domains:
'backupdir'), raise CannotDeleteError(
self.config.get('sysuser', self.delegateto,
'homebackupdir')) "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): if not os.path.isdir(backupdir):
cmdline = 'mkdir -p "%(backupdir)s"' % { cmdline = 'mkdir -p "%(backupdir)s"' % {
'backupdir' : backupdir} 'backupdir' : backupdir}
@ -141,13 +144,11 @@ class Sysuser(BackendEntity):
raise Exception("could not create backup directory") raise Exception("could not create backup directory")
cmdline = 'deluser --remove-home --backup --backup-to "%(backupdir)s" %(username)s' % { cmdline = 'deluser --remove-home --backup --backup-to "%(backupdir)s" %(username)s' % {
'backupdir' : backupdir, 'backupdir' : backupdir,
'username' : self.username} 'username' : self.delegateto.username}
self.sucommand(cmdline) self.sucommand(cmdline)
sysusermapper = mapper(Sysuser, sysuser_table)
class SysuserHandler(BackendEntityHandler): class SysuserHandler(BackendEntityHandler):
"""BackendEntityHandler for Sysuser entities.""" """BackendEntityHandler for Sysuser entities."""
def __init__(self, verbose = False): def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Sysuser, verbose) BackendEntityHandler.__init__(self, SysuserEntity, Sysuser, verbose)

View File

@ -24,82 +24,49 @@ import sys
import migrate.versioning.api import migrate.versioning.api
from settings import * from settings import *
dbversion = migrate.versioning.api.db_version( try:
config.get('database', 'uri'), dbversion = migrate.versioning.api.db_version(
config.get('database', 'repository')) config.get('database', 'uri'),
if dbversion < required_version: config.get('database', 'repository'))
print("""Database version is %d but required version is %d, run if dbversion < required_version:
print("""Database version is %d but required version is %d, run
migrate upgrade %s %s migrate upgrade %s %s
to fix this.""" % to fix this.""" %
(dbversion, required_version, config.get('database', 'uri'), (dbversion, required_version, config.get('database', 'uri'),
config.get('database', 'repository'))) config.get('database', 'repository')))
sys.exit(1)
except exceptions.NoSuchTableError, nste:
print nste
sys.exit(1) sys.exit(1)
meta = BoundMetaData(config.get('database', 'uri')) meta = BoundMetaData(config.get('database', 'uri'))
#meta.engine.echo = True
client_table = Table( client_table = Table(
'client', meta, 'client', meta, schema = dbschema, autoload = True)
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
)
sysuser_table = Table( sysuser_table = Table(
'sysuser', meta, 'sysuser', meta, ForeignKeyConstraint(['clientid'], ['client.clientid']),
Column('sysuserid', Integer, primary_key=True), schema = dbschema, autoload = 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
)
domain_table = Table( domain_table = Table(
'domain', meta, 'domain', meta, ForeignKeyConstraint(['sysuserid'], ['sysuser.sysuserid']),
Column('domainid', Integer, primary_key=True), schema = dbschema, autoload = 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
)
record_table = Table( record_table = Table(
'record', meta, 'record', meta, ForeignKeyConstraint(['domainid'], ['domain.domainid']),
Column('recordid', Integer, primary_key=True), schema = dbschema, autoload = 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
)
supermaster_table = Table( supermaster_table = Table(
'supermaster', meta, 'supermaster', meta,
Column('ip', String(25), nullable=False), ForeignKeyConstraint(['account'], ['sysuser.sysuserid']),
Column('nameserver', String(255), nullable=False), schema = dbschema, autoload = True)
Column('account', Integer, ForeignKey("sysuser.sysuserid"), mailaccount_table = Table(
nullable=False), 'mailaccount', meta,
schema = dbschema 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)

View File

@ -19,7 +19,7 @@
# #
# Version: $Id$ # Version: $Id$
import getopt, sys import getopt, sys, logging
from gnuviechadmin.exceptions import GnuviechadminError from gnuviechadmin.exceptions import GnuviechadminError
class CliCommand: class CliCommand:
@ -198,6 +198,8 @@ Common options:
def __init__(self, args): def __init__(self, args):
"""This initializes the command with the given command line """This initializes the command with the given command line
arguments and executes it.""" arguments and executes it."""
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
self._data = {} self._data = {}
if len(args) > 0: if len(args) > 0:
if args[0] in self._subcommands(): if args[0] in self._subcommands():

View File

@ -59,6 +59,8 @@ class ClientCli(CliCommand.CliCommand):
"the client id", True)])} "the client id", True)])}
def _execute(self, subcommand): def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend import client from gnuviechadmin.backend import client
from gnuviechadmin import exceptions from gnuviechadmin import exceptions
if subcommand == "create": if subcommand == "create":

View File

@ -43,6 +43,8 @@ class DomainCli(CliCommand.CliCommand):
"the domain id", True)])} "the domain id", True)])}
def _execute(self, subcommand): def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend.domain import DomainHandler from gnuviechadmin.backend.domain import DomainHandler
from gnuviechadmin import exceptions from gnuviechadmin import exceptions
if subcommand == "create": if subcommand == "create":

View File

@ -37,16 +37,19 @@ class RecordCli(CliCommand.CliCommand):
(["-p", "--prio"], "prio", (["-p", "--prio"], "prio",
"MX record priority", False), "MX record priority", False),
(["--ttl"], "ttl", (["--ttl"], "ttl",
"Time to live", False), "time to live", False),
(["-d", "--domainid"], "domainid", (["-d", "--domainid"], "domainid",
"Domain id", True)]), "domain id", True)]),
'list' : ("lists existing records", 'list' : ("lists existing records",
[]), [(["-d", "--domainid"], "domainid",
"domain id", False)]),
'delete' : ("delete a record", 'delete' : ("delete a record",
[(["-r", "--recordid"], "recordid", [(["-r", "--recordid"], "recordid",
"the record id", True)])} "the record id", True)])}
def _execute(self, subcommand): def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend.record import RecordHandler from gnuviechadmin.backend.record import RecordHandler
from gnuviechadmin import exceptions from gnuviechadmin import exceptions
if subcommand == "create": if subcommand == "create":
@ -60,7 +63,7 @@ class RecordCli(CliCommand.CliCommand):
print cfe print cfe
sys.exit(2) sys.exit(2)
elif subcommand == "list": elif subcommand == "list":
records = RecordHandler(self._verbose).fetchall() records = RecordHandler(self._verbose).fetchall(**self._data)
for record in records: for record in records:
print record print record
elif subcommand == "delete": elif subcommand == "delete":

View File

@ -47,6 +47,8 @@ class SysuserCli(CliCommand.CliCommand):
"the system user id", True)])} "the system user id", True)])}
def _execute(self, subcommand): def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend import sysuser from gnuviechadmin.backend import sysuser
from gnuviechadmin import exceptions from gnuviechadmin import exceptions
if subcommand == "create": if subcommand == "create":

View File

@ -35,6 +35,13 @@ repository = /etc/gnuviechadmin/dbrepo
[common] [common]
suwrapper = sudo suwrapper = sudo
backupdir = /var/backups/gnuviechadmin 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] [sysuser]
nameprefix = usr nameprefix = usr
@ -46,4 +53,27 @@ shellno = /usr/bin/scponly
defaultgroup = wwwusers defaultgroup = wwwusers
gecos = Webuser %s gecos = Webuser %s
homebackupdir = homes 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.

View File

@ -73,3 +73,16 @@ class ValidationFailedError(GnuviechadminError):
if self.cause: if self.cause:
msg += " The reason is %s." % (str(self.cause)) msg += " The reason is %s." % (str(self.cause))
return msg 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

34
gnuviechadmin/logging.cfg Normal file
View File

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

View File

@ -19,7 +19,7 @@
# #
# Version: $Id$ # Version: $Id$
import os, popen2 import pwd, grp
class PasswdUser(object): class PasswdUser(object):
"""This class represents users in the user database.""" """This class represents users in the user database."""
@ -45,7 +45,7 @@ class PasswdGroup(object):
def __init__(self, groupname, pw, gid, members): def __init__(self, groupname, pw, gid, members):
self.groupname = groupname self.groupname = groupname
self.gid = int(gid) self.gid = int(gid)
self.members = members.split(",") self.members = members
def __repr__(self): def __repr__(self):
return "%s(%s:%d:%s)" % (self.__class__.__name__, return "%s(%s:%d:%s)" % (self.__class__.__name__,
@ -53,42 +53,41 @@ class PasswdGroup(object):
self.gid, self.gid,
",".join(self.members)) ",".join(self.members))
def parsegroups(): def parse_groups():
(stdout, stdin) = popen2.popen2("getent group") return [PasswdGroup(*arr) for arr in grp.getgrall()]
return [PasswdGroup(*arr) for arr in [line.strip().split(":") for line in stdout]]
def parseusers(): def parse_users():
(stdout, stdin) = popen2.popen2("getent passwd") return [PasswdUser(*arr) for arr in pwd.getpwall()]
return [PasswdUser(*arr) for arr in [line.strip().split(":") for line in stdout]]
def finduserbyprefix(prefix): def find_user_by_prefix(prefix):
"""Finds all user entries with the given 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.""" """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: if users:
return users[0] return users[0]
return None return None
def getgroupbyid(gid): def get_group_by_id(gid):
"""Gets the group with the given group id.""" """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: if groups:
return groups[0] return groups[0]
return None return None
def getmaxuid(boundary = 65536): def get_max_uid(boundary = 65536):
"""Gets the highest uid value.""" """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.""" """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__": if __name__ == "__main__":
print "Max UID is %d" % (getmaxuid(40000)) print "Max UID is %d" % (get_max_uid(40000))
print "Max GID is %d" % (getmaxgid(40000)) print "Max GID is %d" % (get_max_gid(40000))
print "User with max UID is %s" % (getuserbyid(getmaxuid(40000))) print "User with max UID is %s" % (get_user_by_id(get_max_uid(40000)))
print "Group with max GID is %s" % (getgroupbyid(getmaxgid(40000))) print "Group with max GID is %s" % (get_group_by_id(get_max_gid(40000)))

View File

@ -21,16 +21,17 @@
import crypt, crack, random 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): def generatepassword(minlength = 8, maxlength = 12):
"""Generates a random password with a length between the given """Generates a random password with a length between the given
minlength and maxlength values.""" 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 \ return "".join([chr(letter) for letter in \
random.sample(pwchars, random.sample(_pwchars,
random.randint(minlength, maxlength))]) random.randint(minlength, maxlength))])
def checkpassword(password): def checkpassword(password):
@ -45,7 +46,7 @@ def checkpassword(password):
def md5_crypt_password(password): def md5_crypt_password(password):
"""Hashes the given password with MD5 and a random salt value.""" """Hashes the given password with MD5 and a random salt value."""
salt = "".join([chr(letter) for letter in \ 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) return crypt.crypt(password, '$1$' + salt)
def get_pw_tuple(password = None): def get_pw_tuple(password = None):