1
0
Fork 0

- restructured

- implementation of client and sysuser cli
- backend for client, sysuser, domain and record
- unified cli binary gva


git-svn-id: file:///home/www/usr01/svn/gnuviechadmin/gnuviech.info/gnuviechadmin/trunk@226 a67ec6bc-e5d5-0310-a910-815c51eb3124
This commit is contained in:
Jan Dittberner 2007-07-02 09:14:47 +00:00
parent ee36146629
commit 926acaddfa
19 changed files with 1010 additions and 345 deletions

View File

@ -1,115 +0,0 @@
#!/usr/bin/python
# -*- 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$
import getopt, sys
from gnuviechadmin import client, exceptions
def usage():
print """Usage: %s [-h|--help] [-v|--verbose]
[-t <title>|--title=<title>]
-f <firstname>|--firstname=<firstname> -l <lastname>|--lastname=<lastname>
-a <address1>|--address=<address1> [--address2=<address2>]
-z <zip>|--zip=<zip> -c <city>|--city=<city> [--country=<isocode>]
[-o <organisation>|--organisation=<organisation>]
-e <email>|--email=<email> -p <phone>|--phone=<phone>
[-m <mobile>|--mobile=<mobile>] [-x <fax>|--fax=<fax>]
General options:
-h, --help show this usage message and exit
-v, --verbose verbose operation
Mandatory client data options:
-f, --firstname firstname
-l, --lastname lastname
-a, --address street address
-z, --zip zip or postal code
-c, --city city or location
-e, --email contact email address
-p, --phone telephone number
Optional client data options:
--address2 optional second line of the street address
-o, --organisation option organisation
--country country (defaults to de)
-t, --title optional title
-m, --mobile optional mobile number
-x, --fax optional fax number
""" % (sys.argv[0])
def main():
try:
opts, args = getopt.gnu_getopt(sys.argv[1:],
"hvf:l:a:z:c:e:p:o:t:m:x:",
["help", "verbose", "firstname=",
"lastname=", "address=", "zip=",
"city=", "email=", "phone=",
"address2=", "organisation=",
"country=", "title=", "mobile=",
"fax="])
except getopt.GetoptError:
usage()
sys.exit(2)
clientdata = {}
verbose = False
for o, a in opts:
if o in ("-v", "--verbose"):
verbose = True
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-f", "--firstname"):
clientdata["firstname"] = a
if o in ("-l", "--lastname"):
clientdata["lastname"] = a
if o in ("-a", "--address"):
clientdata["address1"] = a
if o in ("-z", "--zip"):
clientdata["zip"] = a
if o in ("-c", "--city"):
clientdata["city"] = a
if o == "--country":
clientdata["country"] = a
if o in ("-t", "--title"):
clientdata["title"] = a
if o in ("-m", "--mobile"):
clientdata["mobile"] = a
if o in ("-e", "--email"):
clientdata["email"] = a
if o in ("-o", "--organisation"):
clientdata["organisation"] = a
if o in ("-x", "--fax"):
clientdata["fax"] = a
if o in ("-p", "--phone"):
clientdata["phone"] = a
if verbose:
print "parsed client data is ", clientdata
try:
myclient = client.create(**clientdata)
except exceptions.CreationFailedError, cfe:
usage()
print cfe
sys.exit(2)
if verbose:
print myclient
if __name__ == "__main__":
main()

16
bin/gva
View File

@ -24,14 +24,16 @@ import gnuviechadmin.cli.client
import gnuviechadmin.cli.sysuser
import sys
commands = [gnuviechadmin.cli.client.ClientCli,
gnuviechadmin.cli.sysuser.SysuserCli]
def usage():
print """%s <command> [commandargs]
where command is one of
client - for creating clients
sysuser - for creating system users
""" % sys.argv[0]
for command in commands:
print "%10s - %s" % (command.name, command.description)
def main():
if (sys.argv.__len__() < 2):
@ -39,10 +41,10 @@ def main():
sys.exit()
command = sys.argv[1]
commargs = sys.argv[2:]
if command == "client":
gnuviechadmin.cli.client.ClientCli(commargs)
elif command == "sysuser":
gnuviechadmin.cli.sysuser.SysuserCli(commargs)
if command in [cmd.name for cmd in commands]:
for cmd in commands:
if cmd.name == command:
cmd(commargs)
else:
usage()

View File

@ -0,0 +1,83 @@
# -*- 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$
import ConfigParser, os
from subprocess import *
from sqlalchemy import *
from gnuviechadmin.exceptions import *
class BackendEntity(object):
"""This is the abstract base class for all backend entity classes."""
def __init__(self, verbose = False):
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
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')
toexec = "%s %s" % (suwrapper, cmdline)
if pipedata:
p = Popen(toexec, shell = True, stdin=PIPE)
pipe = p.stdin
print >>pipe, pipedata
pipe.close()
sts = os.waitpid(p.pid, 0)
if self.verbose:
print "%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])
return sts[1]
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 \
if not col.primary_key and not col.nullable]:
if self.__getattribute__(key) is None:
missingfields.append(key)
if missingfields:
raise MissingFieldsError(missingfields)

View File

@ -0,0 +1,69 @@
# -*- 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 *
from gnuviechadmin.exceptions import *
from BackendEntity import *
class BackendEntityHandler(object):
def __init__(self, entityclass, verbose = False):
self.entityclass = entityclass
self.verbose = verbose
def create(self, **kwargs):
try:
entity = self.entityclass(self.verbose, **kwargs)
sess = create_session()
sess.save(entity)
sess.flush()
try:
entity.create_hook()
except:
sess.delete(entity)
sess.flush()
raise
except Exception, e:
raise CreationFailedError(self.entityclass.__name__, e)
def fetchall(self):
"""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
def delete(self, pkvalue):
"""Deletes the entity of the managed entity type that has the
specified primary key value."""
try:
sess = create_session()
entity = sess.query(self.entityclass).get(pkvalue)
if entity:
BackendEntity.__init__(entity, self.verbose)
if self.verbose:
print "delete %s" % (str(entity))
entity.delete_hook()
sess.delete(entity)
sess.flush()
except Exception, e:
raise DeleteFailedError(self.entityclass.__name__, e)

13
bin/listclients → gnuviechadmin/backend/__init__.py Executable file → Normal file
View File

@ -1,4 +1,3 @@
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#
# Copyright (C) 2007 by Jan Dittberner.
@ -17,12 +16,10 @@
# 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 gnuviechadmin import client
"""This is the gnuviechadmin.backend package.
def main():
for row in client.fetchall():
print row
if __name__ == "__main__":
main()
The package provides the backend entities and supporting code for
gnuviechadmin."""

View File

@ -0,0 +1,59 @@
# -*- 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 *
from tables import client_table
from gnuviechadmin.exceptions import *
from BackendEntity import *
from BackendEntityHandler import *
class Client(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
for item in kwargs.items():
self.__setattr__(item)
self.validate()
def create_hook(self):
pass
def delete_hook(self):
pass
client_mapper = mapper(Client, client_table)
client_mapper.add_property("sysusers", relation(sysuser.Sysuser))
class ClientHandler(BackendEntityHandler):
"""BackendEntityHandler for Client entities."""
def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Client, verbose)

View File

@ -0,0 +1,50 @@
# -*- 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: client.py 1101 2007-02-28 21:15:20Z jan $
from sqlalchemy import *
from tables import domain_table
from gnuviechadmin.exceptions import *
import record
from BackendEntity import *
from BackendEntityHandler import *
class Domain(BackendEntity):
"""Entity class for DNS domains."""
_shortkeys = ("domainid", "sysuserid", "name", "type")
def __init__(self, verbose = False, **kwargs):
BackendEntity.__init__(self, verbose)
self.domainid = None
self.sysuserid = None
self.name = None
self.type = None
self.validate()
domain_mapper = mapper(Domain, domain_table)
domain_mapper.add_property("records", relation(record.Record))
class DomainHandler(BackendEntityHandler):
"""BackendEntityHandler for Domain entities."""
def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Domain, verbose)

View File

@ -0,0 +1,52 @@
# -*- 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 *
from tables import record_table
from gnuviechadmin.exceptions import *
from BackendEntity import *
from BackendEntityHandler import *
class Record(BackendEntity):
"""Entity class for DNS domain records."""
_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)
class RecordHandler(BackendEntityHandler):
"""BackendEntityHandler for Record entities."""
def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, Record, verbose)

View File

@ -0,0 +1,153 @@
# -*- 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 *
from tables import sysuser_table
from gnuviechadmin.exceptions import *
from gnuviechadmin.util import passwordutils, getenttools
from BackendEntity import *
from BackendEntityHandler import *
class Sysuser(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
for key in kwargs.keys():
self.__setattr__(key, kwargs[key])
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()
self.validate()
def getnextsysusername(self):
prefix = self.config.get('sysuser', 'nameprefix')
usernames = [user.username for user in \
getenttools.finduserbyprefix(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):
return 1
def gethome(self, sysusername):
"""Gets a valid home directory for the given user name."""
return os.path.join(self.config.get('sysuser', 'homedirbase'),
sysusername)
def getdefaultshell(self):
return False
def getshellbinary(self):
if self.shell:
return self.config.get('sysuser', 'shellyes')
return self.config.get('sysuser', 'shellno')
def getnextsysuid(self):
uid = int(self.config.get('sysuser', 'minuid'))
muid = getenttools.getmaxuid(int(self.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)
cmdline = 'cp -R "%(template)s" "%(home)s"' % {
'template' : templatedir,
'home' : self.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 }
self.sucommand(cmdline)
def create_hook(self):
gecos = self.config.get('sysuser', 'gecos')
gecos = gecos % (self.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'),
'gecos' : gecos,
'username' : self.username}
self.sucommand(cmdline)
cmdline = 'chpasswd --encrypted'
inline = '%(username)s:%(md5pass)s' % {
'username' : self.username,
'md5pass' : self.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'))
if not os.path.isdir(backupdir):
cmdline = 'mkdir -p "%(backupdir)s"' % {
'backupdir' : backupdir}
status = self.sucommand(cmdline)
if status != 0:
raise Exception("could not create backup directory")
cmdline = 'deluser --remove-home --backup --backup-to "%(backupdir)s" %(username)s' % {
'backupdir' : backupdir,
'username' : self.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)

View File

@ -52,9 +52,42 @@ sysuser_table = Table(
Column('home', String(128)),
Column('shell', Boolean, nullable=False, default=False),
Column('clearpass', String(64)),
Column('md5pass', String(32)),
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())
)
sysuser_table.create(checkfirst=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))
domain_table.create(checkfirst=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))
record_table.create(checkfirst=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))
supermaster_table.create(checkfirst=True)

View File

@ -20,62 +20,202 @@
# Version: $Id$
import getopt, sys
from gnuviechadmin.exceptions import GnuviechadminError
class CliCommand:
"""Base class for command line interface."""
def usage(self):
"""This method should print usage information for the command."""
"""Base class for command line interface. A specific
implementation class must define the fields name, description and
_optionmap.
The field name is the name of the subcommand.
The field description is a short, one line description of the
command.
The field _optionmap is a map which maps the subcommand names to
lists of tuples. Each tuple consists of four elements. The first
element is a list of command line arguments, short arguments start
with dash, long arguments with a double dash. The second element
is the name of the field in the data map of the command, it will
directly be sent to the underlying entity. The third field is a
description for the group of command line options in field
one. The fourth field is True for mandatory fields and False
otherwise.
"""
def _usage(self):
"""This method shows usage information. The implementation
relies on the information in the fields name, description and
_optionmap in the implementation classes."""
print """GNUViechAdmin command line interface
Subcommand: %(command)s
%(description)s
Usage:
%(called)s %(command)s -h|--help
gives this usage information.
Common options:
%(option)s
%(mandatory)s %(optiondesc)s
""" % { 'called' : sys.argv[0],
'command' : self.name,
'description' : self.description,
'option' : '-v, --verbose',
'optiondesc' : 'verbose operation',
'mandatory' : " "}
for commandname in self._optionmap.keys():
cmdl = "%(called)s %(command)s %(subcommand)s [-v|--verbose]" % {
'called' : sys.argv[0],
'command' : self.name,
'subcommand' : commandname}
desc = """
%s
""" % (self._optionmap[commandname][0])
for (options, field, optiondesc, mandatory) in \
self._optionmap[commandname][1]:
cmd = " "
pairs = []
for option in options:
if field:
if option.startswith("--"):
pairs.append("%s=<%s>" % (option, field))
else:
pairs.append("%s <%s>" % (option, field))
else:
pairs.append(option)
if not mandatory:
cmd = cmd + "["
cmd = cmd + "|".join(pairs)
if not mandatory:
cmd = cmd + "]"
descmap = {
'option' : ", ".join(pairs),
'optiondesc' : optiondesc,
'mandatory' : ' '}
if mandatory:
descmap['mandatory'] = '*'
desc = desc + """ %(option)s
%(mandatory)s %(optiondesc)s
""" % descmap
if (len(cmdl) + len(cmd)) > 79:
print cmdl
cmdl = cmd
else:
cmdl = cmdl + cmd
print cmdl
print desc
print "Mandatory options are marked with *"
def _subcommands(self):
"""Convenience method for retrieving the subcommand names from
the _optionmap field."""
return self._optionmap.keys()
def _longopts(self, subcommand):
"""This method retrieves available long options in a format
valid for getopt.gnu_getopt(...) from the _optionmap field."""
longopts = []
for cur in [(option[0], option[1]) for option in \
self._optionmap[subcommand][1]]:
for command in cur[0]:
if command.startswith("--"):
if cur[1]:
longopts.append(command[2:] + "=")
else:
longopts.append(command[2:])
return longopts
def _shortopts(self, subcommand):
"""This method retrieves available short options in a format
valid for getopt.gnu_getopt(...) from the _optionmap field."""
shortopts = ""
for cur in [(option[0], option[1]) for option in \
self._optionmap[subcommand][1]]:
for command in cur[0]:
if not command.startswith("--"):
if cur[1]:
shortopts = shortopts + command[1:] + ":"
else:
shortopts = shortopts + command[1:]
return shortopts
def _checkrequired(self, subcommand):
"""Checks whether the required fields of the given subcommand
are set."""
reqcheck = [True, []]
for req in [option for option in \
self._optionmap[subcommand][1] if option[3]]:
if not req[1] in self._data:
reqcheck[0] = False
reqcheck[1].append(""" %s
* %s""" % (", ".join(req[0]), req[2]))
return reqcheck
def _handleoption(self, subcommand, o, a):
"""Handles a command line option by assigning it to the
matching key as defined in the _optionmap property of the
implementation class."""
optionmap = [(option[0], option[1]) for option in \
self._optionmap[subcommand][1]]
if optionmap:
for (options, datakey) in optionmap:
if o in options:
self._data[datakey] = a
def _execute(self, subcommand):
"""This method is called when the subcommand of the command is
executed."""
raise NotImplementedError
def shortopts(self):
"""This method should return an option string for the short
options for getopt.gnu_getopt(...)."""
raise NotImplementedError
def longopts(self):
"""This method should return a list of long options for
getopt.gnu_getopt(...)."""
raise NotImplementedError
def handleoption(self, option, argument):
"""This method should handle each option known to the command."""
raise NotImplementedError
def execute(self):
"""This method is called when the command is executed."""
raise NotImplementedError
def checkrequired(self):
"""This methode is called after handling command line options
and should check whether all required values were set."""
raise NotImplementedError
def __parseopts(self, args):
def _parseopts(self, subcommand, args):
"""This method parses the options given on the command line."""
longopts = ["help", "verbose"]
longopts.extend(self.longopts())
longopts.extend(self._longopts(subcommand))
try:
opts, args = getopt.gnu_getopt(
args,
"hv" + self.shortopts(),
"hv" + self._shortopts(subcommand),
longopts)
except getopt.GetoptError:
self.usage()
self._usage()
sys.exit(2)
self.verbose = False
self._verbose = False
for o, a in opts:
if o in ("-v", "--verbose"):
self.verbose = True
self._verbose = True
if o in ("-h", "--help"):
self.usage()
self._usage()
sys.exit()
self.handleoption(o, a)
self._handleoption(subcommand, o, a)
def __init__(self, args):
"""This initializes the command with the given command line
arguments and executes it."""
self.__parseopts(args)
if (self.checkrequired()):
self.execute()
self._data = {}
if len(args) > 0:
if args[0] in self._subcommands():
self._parseopts(args[0], args[1:])
reqcheck = self._checkrequired(args[0])
if reqcheck[0]:
try:
self._execute(args[0])
except GnuviechadminError, e:
print e
else:
self.usage()
self._usage()
print """
the following required arguments are missing:
"""
print "\n".join(reqcheck[1])
else:
self._usage()
print "invalid sub command"
else:
self._usage()

View File

@ -20,91 +20,63 @@
# Version: $Id$
import CliCommand, sys
from gnuviechadmin import client
class ClientCli(CliCommand.CliCommand):
"""Command line interface command for client creation."""
def shortopts(self):
return "f:l:a:z:c:e:p:o:t:m:x:"
"""Command line interface command for client managament."""
def longopts(self):
return ["firstname=", "lastname=", "address=", "zip=",
"city=", "email=", "phone=", "address2=", "organisation=",
"country=", "title=", "mobile=", "fax="]
name = "client"
description = "manage clients"
_optionmap = {
'create' : ("creates a new client",
[(["-f", "--firstname"], "firstname",
"the client's first name", True),
(["-l", "--lastname"], "lastname",
"the client's last name", True),
(["-t", "--title"], "title",
"the client's title", False),
(["-a", "--address"], "address1",
"the address of the client", True),
(["--address2"], "address2",
"second line of the client's address", False),
(["-z", "--zip"], "zip",
"the zipcode of the client's address", True),
(["-c", "--city"], "city",
"the city of the client's address", True),
(["--country"], "country",
"the client's country", False),
(["-e", "--email"], "email",
"the client's email address", True),
(["-p", "--phone"], "phone",
"the client's phone number", True),
(["-m", "--mobile"], "mobile",
"the client's mobile phone number", False),
(["-x", "--fax"], "fax",
"the client's fax number", False)]),
'list' : ("lists existing clients",
[]),
'delete' : ("deletes the specified client if it has no dependent data",
[(["-c", "--clientid"], "clientid",
"the client id", True)])}
def usage(self):
print """Usage: %s client [-h|--help] [-v|--verbose]
[-t <title>|--title=<title>]
-f <firstname>|--firstname=<firstname> -l <lastname>|--lastname=<lastname>
-a <address1>|--address=<address1> [--address2=<address2>]
-z <zip>|--zip=<zip> -c <city>|--city=<city> [--country=<isocode>]
[-o <organisation>|--organisation=<organisation>]
-e <email>|--email=<email> -p <phone>|--phone=<phone>
[-m <mobile>|--mobile=<mobile>] [-x <fax>|--fax=<fax>]
General options:
-h, --help show this usage message and exit
-v, --verbose verbose operation
Mandatory client data options:
-f, --firstname firstname
-l, --lastname lastname
-a, --address street address
-z, --zip zip or postal code
-c, --city city or location
-e, --email contact email address
-p, --phone telephone number
Optional client data options:
--address2 optional second line of the street address
-o, --organisation option organisation
--country country (defaults to de)
-t, --title optional title
-m, --mobile optional mobile number
-x, --fax optional fax number
""" % sys.argv[0]
def handleoption(self, o, a):
if o in ("-f", "--firstname"):
self.data["firstname"] = a
elif o in ("-l", "--lastname"):
self.data["lastname"] = a
elif o in ("-a", "--address"):
self.data["address1"] = a
elif o in ("-z", "--zip"):
self.data["zip"] = a
elif o in ("-c", "--city"):
self.data["city"] = a
elif o == "--country":
self.data["country"] = a
elif o in ("-t", "--title"):
self.data["title"] = a
elif o in ("-m", "--mobile"):
self.data["mobile"] = a
elif o in ("-e", "--email"):
self.data["email"] = a
elif o in ("-o", "--organisation"):
self.data["organisation"] = a
elif o in ("-x", "--fax"):
self.data["fax"] = a
elif o in ("-p", "--phone"):
self.data["phone"] = a
def checkrequired(self):
required = ['firstname', 'lastname', 'address1', 'zip', 'city',
'phone', 'email']
if self.verbose:
print self.data
for req in required:
if not req in self.data:
return False
return True
def execute(self):
myclient = client.create(**self.data)
if self.verbose:
def _execute(self, subcommand):
from gnuviechadmin.backend import client
from gnuviechadmin import exceptions
if subcommand == "create":
try:
myclient = client.ClientHandler(self._verbose).create(
**self._data)
if self._verbose:
print myclient
except exceptions.CreationFailedError, cfe:
self._usage()
print cfe
sys.exit(2)
elif subcommand == "list":
clients = client.ClientHandler(self._verbose).fetchall()
for client in clients:
print client
elif subcommand == "delete":
client.ClientHandler(self._verbose).delete(self._data["clientid"])
def __init__(self, argv):
self.data = {}
CliCommand.CliCommand.__init__(self, argv)

View File

@ -1 +1,71 @@
pass
# -*- 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$
import CliCommand, sys
class SysuserCli(CliCommand.CliCommand):
"""Command line interface command for system user managament."""
name = "sysuser"
description = "manage system users"
_optionmap = {
"create" : ("create a new system user with the given options.",
[(["-n", "--username"], "username",
"the system user name", False),
(["-t", "--usertype"], "usertype",
"the numeric user type", False),
(["-h", "--home"], "home",
"the home directory", False),
(["-s", "--shell"], "shell",
"true if the user should get shell access", False),
(["-p", "--password"], "clearpass",
"the password for the user", False),
(["-c", "--clientid"], "clientid",
"the client id", True)]),
"list" : ("list existing system users.",
[]),
"delete" : ("delete a system user.",
[(["-s", "--sysuserid"], "sysuserid",
"the system user id", True)])}
def _execute(self, subcommand):
from gnuviechadmin.backend import sysuser
from gnuviechadmin import exceptions
if subcommand == "create":
try:
mysysuser = sysuser.SysuserHandler(self._verbose).create(
**self._data)
if self._verbose:
print mysysuser
except exceptions.CreationFailedError, cfe:
self._usage()
print cfe
sys.exit(2)
elif subcommand == "list":
sysusers = sysuser.SysuserHandler(self._verbose).fetchall()
for su in sysusers:
print su
elif subcommand == "delete":
sysuser.SysuserHandler(self._verbose).delete(
self._data["sysuserid"])
def __init__(self, argv):
CliCommand.CliCommand.__init__(self, argv)

View File

@ -1,76 +0,0 @@
# -*- 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 *
from gnuviechadmin.tables import *
from gnuviechadmin import sysuser
from gnuviechadmin.exceptions import *
class Client(object):
mandatory = ('firstname', 'lastname', 'address1', 'zip', 'city',
'country', 'phone', 'email')
"""This class provides a client representation"""
def __init__(self, **kwargs):
self.clientid = None
self.country = 'de'
self.title = None
self.address2 = None
self.mobile = None
self.fax = None
self.organisation = None
for key in kwargs.keys():
self.__setattr__(key, kwargs[key])
self.validate()
def validate(self):
missingfields = []
for key in self.mandatory:
if self.__getattribute__(key) is None:
missingfields.append(key)
if missingfields:
raise MissingFieldsError(missingfields)
def __repr__(self):
return "%(clientid)d,%(firstname)s,%(lastname)s,%(email)s" % (
{'clientid' : self.clientid, 'firstname' : self.firstname,
'lastname' : self.lastname, 'email' : self.email})
clientmapper = mapper(
Client, client_table,
properties = {'sysusers': relation(sysuser.Sysuser)})
def create(**kwargs):
try:
myclient = Client(**kwargs)
sess = create_session()
sess.save(myclient)
sess.flush()
except MissingFieldsError, mfe:
raise CreationFailedError(Client.__name__, mfe)
except exceptions.SQLError, sqle:
raise CreationFailedError(Client.__name__, sqle)
return myclient
def fetchall():
session = create_session()
query = session.query(Client)
return query.select()

View File

@ -32,3 +32,19 @@
# very usable for a real installation.
#
uri = sqlite:///:memory:
[common]
suwrapper = sudo
backupdir = /var/backups/gnuviechadmin
[sysuser]
nameprefix = usr
homedirbase = /home/www
minuid = 10000
maxuid = 39999
shellyes = /bin/bash
shellno = /usr/bin/scponly
defaultgroup = wwwusers
gecos = Webuser %s
homebackupdir = homes
hometemplate = /etc/gnuviechadmin/templates/home

View File

@ -20,7 +20,13 @@
# Version: $Id$
"""This file defines the gnuviechadmin specific exception types."""
class MissingFieldsError(Exception):
class GnuviechadminError(Exception):
"""This is the base class for domain specific exceptions of
Gnuviechadmin."""
pass
class MissingFieldsError(GnuviechadminError):
"""This exception should be raised when a required field of a data
class is missing."""
def __init__(self, missingfields):
@ -29,7 +35,7 @@ class is missing."""
def __str__(self):
return "the fields %s are missing." % (repr(self.missing))
class CreationFailedError(Exception):
class CreationFailedError(GnuviechadminError):
"""This exception should be raised if a business object could not
be created."""
def __init__(self, classname, cause = None):
@ -41,3 +47,16 @@ be created."""
if self.cause:
msg += " The reason is %s." % (str(self.cause))
return msg
class DeleteFailedError(GnuviechadminError):
"""This exception should be raise if a business object coild not
be deleted."""
def __init__(self, classname, cause = None):
self.classname = classname
self.cause = cause
def __str__(self):
msg = "Deleting an instance of class %s failed." % (self.classname)
if self.cause:
msg += " The reason is %s." % (str(self.cause))
return msg

View File

@ -19,16 +19,6 @@
#
# Version: $Id$
from sqlalchemy import *
from gnuviechadmin.tables import *
from gnuviechadmin.exceptions import *
"""This is the gnuviechadmin.util package.
class Sysuser(object):
def __repr__(self):
return "%(sysuserid)d,%(username)s,%(clientid)d,%(sysuid)d" % ({
'sysuserid': self.sysuserid,
'username': self.username,
'clientid': self.clientid,
'sysuid': self.sysuid})
sysusermapper = mapper(Sysuser, sysuser_table)
The package provides utility modules for various functions."""

View File

@ -0,0 +1,94 @@
# -*- 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$
import os, popen2
class PasswdUser(object):
"""This class represents users in the user database."""
def __init__(self, username, pw, uid, gid, gecos, home, shell):
self.username = username
self.uid = int(uid)
self.gid = int(gid)
self.gecos = gecos
self.home = home
self.shell = shell
def __repr__(self):
return "%s(%s:%d:%d:%s:%s:%s)" % (self.__class__.__name__,
self.username,
self.uid,
self.gid,
self.gecos,
self.home,
self.shell)
class PasswdGroup(object):
"""This class represents lines in the groups database."""
def __init__(self, groupname, pw, gid, members):
self.groupname = groupname
self.gid = int(gid)
self.members = members.split(",")
def __repr__(self):
return "%s(%s:%d:%s)" % (self.__class__.__name__,
self.groupname,
self.gid,
",".join(self.members))
def parsegroups():
(stdout, stdin) = popen2.popen2("getent group")
return [PasswdGroup(*arr) for arr in [line.strip().split(":") for line in stdout]]
def parseusers():
(stdout, stdin) = popen2.popen2("getent passwd")
return [PasswdUser(*arr) for arr in [line.strip().split(":") for line in stdout]]
def finduserbyprefix(prefix):
"""Finds all user entries with the given prefix."""
return [user for user in parseusers() if user.username.startswith(prefix)]
def getuserbyid(uid):
"""Gets the user with the given user id."""
users = [user for user in parseusers() if user.uid == uid]
if users:
return users[0]
return None
def getgroupbyid(gid):
"""Gets the group with the given group id."""
groups = [group for group in parsegroups() if group.gid == gid]
if groups:
return groups[0]
return None
def getmaxuid(boundary = 65536):
"""Gets the highest uid value."""
return max([user.uid for user in parseusers() if user.uid <= boundary])
def getmaxgid(boundary = 65536):
"""Gets the highest gid value."""
return max([group.gid for group in parsegroups() if group.gid <= boundary])
if __name__ == "__main__":
print "Max UID is %d" % (getmaxuid(40000))
print "Max GID is %d" % (getmaxgid(40000))
print "User with max UID is %s" % (getuserbyid(getmaxuid(40000)))
print "Group with max GID is %s" % (getgroupbyid(getmaxgid(40000)))

View File

@ -0,0 +1,57 @@
# -*- 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$
import crypt, crack, random
def generatepassword(minlength = 8, maxlength = 12):
"""Generates a random password with a length between the given
minlength and maxlength values."""
pwchars = []
for pair in (('0', '9'), ('A', 'Z'), ('a', 'z')):
pwchars.extend(range(ord(pair[0]), ord(pair[1])))
for char in "-+/*_@":
pwchars.append(ord(char))
return "".join([chr(letter) for letter in \
random.sample(pwchars,
random.randint(minlength, maxlength))])
def checkpassword(password):
"""Checks the password with cracklib. The password is returned if
it is good enough. Otherwise None is returned."""
try:
return crack.VeryFascistCheck(password)
except ValueError, ve:
print "Weak password:", ve
return None
def md5_crypt_password(password):
"""Hashes the given password with MD5 and a random salt value."""
salt = "".join([chr(letter) for letter in \
random.sample(range(ord('a'), ord('z')), 8)])
return crypt.crypt(password, '$1$' + salt)
def get_pw_tuple(password = None):
"""Gets a valid tuple consisting of a password and a md5 hash of the
password. If a password is given it is checked and if it is too weak
replaced by a generated one."""
while password == None or checkpassword(password) == None:
password = generatepassword()
return (password, md5_crypt_password(password))