#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # PyKota Users Manager # # PyKota - Print Quotas for CUPS and LPRng # # (c) 2003, 2004, 2005, 2006, 2007 Jerome Alet # 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. # # $Id$ # # import sys import pwd import grp from pykota.tool import Percent, PyKotaTool, PyKotaCommandLineError, crashed, N_ from pykota.storage import StorageUser, StorageGroup __doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s An Users and Groups Manager for PyKota. command line usage : pkusers [options] user1 user2 user3 ... userN or : pkusers --groups [options] group1 group2 group3 ... groupN options : -v | --version Prints pkusers's version number then exits. -h | --help Prints this message then exits. -a | --add Adds users if they don't exist on the database. If they exist, they are modified unless -s|--skipexisting is also used. -d | --delete Deletes users from the quota storage. -e | --email addr Sets the email address for the users. If the addr parameter begins with @, then the username is prepended to addr to form a valid email address. -D | --description d Adds a textual description to users or groups. -g | --groups Edit users groups instead of users. -o | --overcharge f Sets the overcharging factor applied to the user when computing the cost of a print job. Positive or negative floating point values are allowed, this allows you to do some really creative things like giving money to an user whenever he prints. The number of pages in a print job is not modified by this coefficient, only the cost of the job for a particular user. Only users have such a coefficient. -i | --ingroups g1[,g2...] Puts the users into each of the groups listed, separated by commas. The groups must already exist in the Quota Storage. -L | --list Lists users or groups. -l | --limitby l Choose if the user/group is limited in printing by its account balance or by its page quota. The default value is 'quota'. Allowed values are 'quota' 'balance' 'noquota' 'noprint' and 'nochange' : - quota : limit by number of pages per printer. - balance : limit by number of credits in account. - noquota : no limit, accounting still done. - nochange : no limit, accounting not done. - noprint : printing is denied. NB : nochange and noprint are not supported for groups. -b | --balance b Sets the user's account balance to b. Account balance may be increase or decreased if b is prefixed with + or -. WARNING : when decreasing account balance, the total paid so far by the user is decreased too. Groups don't have a real balance, but the sum of their users' account balance. -C | --comment txt Defines some informational text to be associated with a change to an user's account balance. Only meaningful if -b | --balance is also used. -r | --remove In combination with the --ingroups option above, remove users from the specified users groups. -s | --skipexisting In combination with the --add option above, tells pkusers to not modify existing users. user1 through userN and group1 through groupN can use wildcards if the --add option is not set. examples : $ pkusers --add john paul george ringo/ringo@example.com This will add users john, paul, george and ringo to the quota database. User ringo's email address will also be set to 'ringo@example.com' $ pkusers --ingroups coders,it jerome User jerome is put into the groups "coders" and "it" which must already exist in the quota database. $ pkusers --limitby balance jerome This will tell PyKota to limit jerome by his account's balance when printing. $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome This will increase jerome's account balance by 10.0 (in your own currency). You can decrease the account balance with a dash prefix, and set it to a fixed amount with no prefix. A comment will be stored for this balance change. $ pkusers --delete jerome rachel This will completely delete jerome and rachel from the quota database. All their quotas and jobs will be deleted too. $ pkusers --overcharge 2.5 poorstudent This will overcharge the poorstudent user by a factor of 2.5. $ pkusers --overcharge -1 jerome User jerome will actually earn money whenever he prints. $ pkusers --overcharge 0 boss User boss can print at will, it won't cost him anything because the cost of each print job will be multiplied by zero before charging his account. $ pkusers --email @example.com This will set the email address for each user to username@example.com """) class PKUsers(PyKotaTool) : """A class for a users and users groups manager.""" def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) : """Modifies an entry.""" if description is not None : # NB : "" is allowed ! entry.setDescription(description) if limitby : entry.setLimitBy(limitby) if not groups : if email is not None : # we allow "" to empty the field if email.startswith("@") : email = "%s%s" % (entry.Name, email) if email and email.count('@') != 1 : raise PyKotaCommandLineError, _("Invalid email address %s") % email entry.setEmail(email) if overcharge is not None : # NB : 0 is allowed ! entry.setOverChargeFactor(overcharge) if balance : if balance.startswith("+") or balance.startswith("-") : newbalance = float(entry.AccountBalance or 0.0) + balancevalue newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue entry.setAccountBalance(newbalance, newlifetimepaid, comment) else : diff = balancevalue - float(entry.AccountBalance or 0.0) newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff entry.setAccountBalance(balancevalue, newlifetimepaid, comment) def manageUsersGroups(self, ugroups, user, remove) : """Manage user group membership.""" for ugroup in ugroups : if remove : ugroup.delUserFromGroup(user) else : ugroup.addUserToGroup(user) def main(self, names, options) : """Manage users or groups.""" names = self.sanitizeNames(options, names) suffix = (options["groups"] and "Group") or "User" if not options["list"] : percent = Percent(self) if not options["add"] : if not options["list"] : percent.display("%s..." % _("Extracting datas")) if not names : # NB : can't happen for --delete because it's catched earlier names = ["*"] entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names)) if not entries : if not options["list"] : percent.display("\n") raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names)) if not options["list"] : percent.setSize(len(entries)) if options["list"] : if suffix == "User" : maildomain = self.config.getMailDomain() smtpserver = self.config.getSMTPServer() for entry in entries : email = entry.Email if not email : if maildomain : email = "%s@%s" % (entry.Name, maildomain) elif smtpserver : email = "%s@%s" % (entry.Name, smtpserver) else : email = "%s@%s" % (entry.Name, "localhost") msg = "%s - <%s>" % (entry.Name, email) if entry.Description : msg += " - %s" % entry.Description print msg print " %s" % (_("Limited by : %s") % entry.LimitBy) print " %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0)) print " %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0)) print " %s" % (_("Overcharging factor : %.2f") % entry.OverCharge) print else : for entry in entries : msg = "%s" % entry.Name if entry.Description : msg += " - %s" % entry.Description print msg print " %s" % (_("Limited by : %s") % entry.LimitBy) print " %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0)) print " %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0)) print elif options["delete"] : percent.display("\n%s..." % _("Deletion")) getattr(self.storage, "deleteMany%ss" % suffix)(entries) percent.display("\n") else : limitby = options["limitby"] if limitby : limitby = limitby.strip().lower() if limitby : if limitby not in ('quota', 'balance', 'noquota', \ 'noprint', 'nochange') : raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"] if (limitby in ('nochange', 'noprint')) and options["groups"] : raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"] overcharge = options["overcharge"] if overcharge : try : overcharge = float(overcharge.strip()) except (ValueError, AttributeError) : raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"] balance = options["balance"] if balance : balance = balance.strip() try : balancevalue = float(balance) except ValueError : raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"] else : balancevalue = None if options["ingroups"] : usersgroups = self.storage.getMatchingGroups(options["ingroups"]) if not usersgroups : raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(',')) else : usersgroups = [] description = options["description"] if description : description = description.strip() comment = options["comment"] if comment : comment = comment.strip() email = options["email"] if email : email = email.strip() skipexisting = options["skipexisting"] groups = options["groups"] remove = options["remove"] self.storage.beginTransaction() try : if options["add"] : rejectunknown = self.config.getRejectUnknown() percent.display("%s...\n" % _("Creation")) percent.setSize(len(names)) for ename in names : useremail = None if not groups : splitname = ename.split('/', 1) # username/email if len(splitname) == 1 : splitname.append("") (ename, useremail) = splitname if self.isValidName(ename) : reject = 0 if rejectunknown : if groups : try : grp.getgrnam(ename) except KeyError : self.printInfo(_("Unknown group %s") % ename, "error") reject = 1 else : try : pwd.getpwnam(ename) except KeyError : self.printInfo(_("Unknown user %s") % ename, "error") reject = 1 if not reject : entry = globals()["Storage%s" % suffix](self.storage, ename) if groups : self.modifyEntry(entry, groups, limitby, \ description) else : self.modifyEntry(entry, groups, limitby, \ description, overcharge,\ balance, balancevalue, \ comment, useremail or email) oldentry = getattr(self.storage, "add%s" % suffix)(entry) if oldentry is not None : if skipexisting : self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename)) else : self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename)) if groups : self.modifyEntry(oldentry, groups, \ limitby, description) else : self.modifyEntry(oldentry, groups, limitby, \ description, overcharge,\ balance, balancevalue, \ comment, useremail or email) oldentry.save() if not groups : self.manageUsersGroups(usersgroups, oldentry, remove) elif usersgroups and not groups : self.manageUsersGroups(usersgroups, \ self.storage.getUser(ename), \ remove) else : raise PyKotaCommandLineError, _("Invalid name %s") % ename percent.oneMore() else : percent.display("\n%s...\n" % _("Modification")) for entry in entries : if groups : self.modifyEntry(entry, groups, limitby, description) else : self.modifyEntry(entry, groups, limitby, description, \ overcharge, balance, balancevalue, \ comment, email) self.manageUsersGroups(usersgroups, entry, remove) entry.save() percent.oneMore() except : self.storage.rollbackTransaction() raise else : self.storage.commitTransaction() if not options["list"] : percent.done() if __name__ == "__main__" : retcode = 0 try : defaults = { \ "comment" : "", \ } short_options = "hvaD:dgl:rso:i:b:C:Le:" long_options = ["help", "version", "add", "description=", \ "delete", "groups", "list", "remove", \ "skipexisting", "overcharge=", "email=", \ "ingroups=", "limitby=", "balance=", "comment=", \ ] # Initializes the command line tool manager = PKUsers(doc=__doc__) manager.deferredInit() # parse and checks the command line (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options) # sets long options options["help"] = options["h"] or options["help"] options["version"] = options["v"] or options["version"] options["add"] = options["a"] or options["add"] options["description"] = options["D"] or options["description"] options["delete"] = options["d"] or options["delete"] options["groups"] = options["g"] or options["groups"] options["list"] = options["L"] or options["list"] options["remove"] = options["r"] or options["remove"] options["skipexisting"] = options["s"] or options["skipexisting"] options["limitby"] = options["l"] or options["limitby"] options["balance"] = options["b"] or options["balance"] options["ingroups"] = options["i"] or options["ingroups"] options["overcharge"] = options["o"] or options["overcharge"] options["comment"] = options["C"] or options["comment"] or defaults["comment"] options["email"] = options["e"] or options["email"] if options["help"] : manager.display_usage_and_quit() elif options["version"] : manager.display_version_and_quit() elif (options["delete"] and (options["add"] or options["remove"] or options["description"] or options["email"])) \ or (options["skipexisting"] and not options["add"]) \ or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"] or options["email"])) \ or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) : raise PyKotaCommandLineError, _("incompatible options, see help.") elif options["remove"] and not options["ingroups"] : raise PyKotaCommandLineError, _("You have to pass user groups names on the command line") elif (not args) and (options["add"] or options["delete"]) : raise PyKotaCommandLineError, _("You have to pass user or group names on the command line") else : retcode = manager.main(args, options) except KeyboardInterrupt : sys.stderr.write("\nInterrupted with Ctrl+C !\n") retcode = -3 except PyKotaCommandLineError, msg : sys.stderr.write("%s : %s\n" % (sys.argv[0], msg)) retcode = -2 except SystemExit : pass except : try : manager.crashed("pkusers failed") except : crashed("pkusers failed") retcode = -1 try : manager.storage.close() except (TypeError, NameError, AttributeError) : pass sys.exit(retcode)