1 | #! /usr/bin/env python |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | # |
---|
4 | # PyKota : Print Quotas for CUPS |
---|
5 | # |
---|
6 | # (c) 2003-2009 Jerome Alet <alet@librelogiciel.com> |
---|
7 | # This program is free software: you can redistribute it and/or modify |
---|
8 | # it under the terms of the GNU General Public License as published by |
---|
9 | # the Free Software Foundation, either version 3 of the License, or |
---|
10 | # (at your option) any later version. |
---|
11 | # |
---|
12 | # This program is distributed in the hope that it will be useful, |
---|
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
15 | # GNU General Public License for more details. |
---|
16 | # |
---|
17 | # You should have received a copy of the GNU General Public License |
---|
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
---|
19 | # |
---|
20 | # $Id$ |
---|
21 | # |
---|
22 | # |
---|
23 | |
---|
24 | """This command line tool can automatically send periodic email |
---|
25 | notifications to users or groups who have reached the limit of their |
---|
26 | printing quota.""" |
---|
27 | |
---|
28 | import sys |
---|
29 | import os |
---|
30 | import pwd |
---|
31 | import socket |
---|
32 | import smtplib |
---|
33 | from email.MIMEText import MIMEText |
---|
34 | from email.Header import Header |
---|
35 | import email.Utils |
---|
36 | |
---|
37 | import pykota.appinit |
---|
38 | from pykota.utils import run |
---|
39 | from pykota.commandline import PyKotaOptionParser |
---|
40 | from pykota.errors import PyKotaCommandLineError |
---|
41 | from pykota.tool import PyKotaTool |
---|
42 | |
---|
43 | class WarnPyKota(PyKotaTool) : |
---|
44 | """A class for warnpykota.""" |
---|
45 | def sendMessage(self, adminmail, touser, fullmessage) : |
---|
46 | """Sends an email message containing headers to some user.""" |
---|
47 | smtpserver = self.smtpserver |
---|
48 | try : |
---|
49 | server = smtplib.SMTP(smtpserver) |
---|
50 | except socket.error, msg : |
---|
51 | self.printInfo(_("Impossible to connect to SMTP server : %(smtpserver)s") \ |
---|
52 | % locals(), \ |
---|
53 | "error") |
---|
54 | else : |
---|
55 | try : |
---|
56 | server.sendmail(adminmail, [touser], fullmessage) |
---|
57 | except smtplib.SMTPException, answer : |
---|
58 | for (k, v) in answer.recipients.items() : |
---|
59 | errormsg = v[0] |
---|
60 | errorvalue = v[1] |
---|
61 | self.printInfo(_("Impossible to send mail to %(touser)s, error %(errormsg)s : %(errorvalue)s") \ |
---|
62 | % locals(), \ |
---|
63 | "error") |
---|
64 | server.quit() |
---|
65 | |
---|
66 | def sendMessageToUser(self, admin, adminmail, user, subject, message) : |
---|
67 | """Sends an email message to a user.""" |
---|
68 | message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail) |
---|
69 | usermail = user.Email or user.Name |
---|
70 | if "@" not in usermail : |
---|
71 | usermail = "%s@%s" % (usermail, self.maildomain or self.smtpserver or "localhost") |
---|
72 | msg = MIMEText(message.encode(self.charset, "replace"), _charset=self.charset) |
---|
73 | msg["Subject"] = Header(subject.encode(self.charset, "replace"), charset=self.charset) |
---|
74 | msg["From"] = adminmail |
---|
75 | msg["To"] = usermail |
---|
76 | msg["Date"] = email.Utils.formatdate(localtime=True) |
---|
77 | self.sendMessage(adminmail, usermail, msg.as_string()) |
---|
78 | |
---|
79 | def sendMessageToAdmin(self, adminmail, subject, message) : |
---|
80 | """Sends an email message to the Print Quota administrator.""" |
---|
81 | if "@" not in adminmail : |
---|
82 | adminmail = "%s@%s" % (adminmail, self.maildomain or self.smtpserver or "localhost") |
---|
83 | msg = MIMEText(message.encode(self.charset, "replace"), _charset=self.charset) |
---|
84 | msg["Subject"] = Header(subject.encode(self.charset, "replace"), charset=self.charset) |
---|
85 | msg["From"] = adminmail |
---|
86 | msg["To"] = adminmail |
---|
87 | self.sendMessage(adminmail, adminmail, msg.as_string()) |
---|
88 | |
---|
89 | def warnGroupPQuota(self, grouppquota) : |
---|
90 | """Checks a group quota and send messages if quota is exceeded on current printer.""" |
---|
91 | group = grouppquota.Group |
---|
92 | groupname = group.Name |
---|
93 | printer = grouppquota.Printer |
---|
94 | printername = printer.Name |
---|
95 | admin = self.config.getAdmin(printername) |
---|
96 | adminmail = self.config.getAdminMail(printername) |
---|
97 | (mailto, arguments) = self.config.getMailTo(printername) |
---|
98 | if group.LimitBy in ("noquota", "nochange") : |
---|
99 | action = "ALLOW" |
---|
100 | else : |
---|
101 | action = self.checkGroupPQuota(grouppquota) |
---|
102 | if action.startswith("POLICY_") : |
---|
103 | action = action[7:] |
---|
104 | if action == "DENY" : |
---|
105 | adminmessage = _("Print Quota exceeded for group %(groupname)s on printer %(printername)s") % locals() |
---|
106 | self.printInfo(adminmessage) |
---|
107 | if mailto in [ "BOTH", "ADMIN" ] : |
---|
108 | self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) |
---|
109 | if mailto in [ "BOTH", "USER", "EXTERNAL" ] : |
---|
110 | for user in self.storage.getGroupMembers(group) : |
---|
111 | if mailto != "EXTERNAL" : |
---|
112 | self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printername)) |
---|
113 | else : |
---|
114 | self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printername)) |
---|
115 | elif action == "WARN" : |
---|
116 | adminmessage = _("Print Quota low for group %(groupname)s on printer %(printername)s") % locals() |
---|
117 | self.printInfo(adminmessage) |
---|
118 | if mailto in [ "BOTH", "ADMIN" ] : |
---|
119 | self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) |
---|
120 | if group.LimitBy and (group.LimitBy.lower() == "balance") : |
---|
121 | message = self.config.getPoorWarn() |
---|
122 | else : |
---|
123 | message = self.config.getSoftWarn(printername) |
---|
124 | if mailto in [ "BOTH", "USER", "EXTERNAL" ] : |
---|
125 | for user in self.storage.getGroupMembers(group) : |
---|
126 | if mailto != "EXTERNAL" : |
---|
127 | self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message) |
---|
128 | else : |
---|
129 | self.externalMailTo(arguments, action, user, printer, message) |
---|
130 | return action |
---|
131 | |
---|
132 | def warnUserPQuota(self, userpquota) : |
---|
133 | """Checks a user quota and send him a message if quota is exceeded on current printer.""" |
---|
134 | user = userpquota.User |
---|
135 | username = user.Name |
---|
136 | printer = userpquota.Printer |
---|
137 | printername = printer.Name |
---|
138 | admin = self.config.getAdmin(printername) |
---|
139 | adminmail = self.config.getAdminMail(printername) |
---|
140 | (mailto, arguments) = self.config.getMailTo(printername) |
---|
141 | |
---|
142 | if user.LimitBy in ("noquota", "nochange") : |
---|
143 | action = "ALLOW" |
---|
144 | elif user.LimitBy == "noprint" : |
---|
145 | action = "DENY" |
---|
146 | message = _("User %(username)s is not allowed to print at this time.") % locals() |
---|
147 | self.printInfo(message) |
---|
148 | if mailto in [ "BOTH", "USER", "EXTERNAL" ] : |
---|
149 | if mailto != "EXTERNAL" : |
---|
150 | self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message) |
---|
151 | else : |
---|
152 | self.externalMailTo(arguments, action, user, printer, message) |
---|
153 | if mailto in [ "BOTH", "ADMIN" ] : |
---|
154 | self.sendMessageToAdmin(adminmail, _("Print Quota"), message) |
---|
155 | else : |
---|
156 | action = self.checkUserPQuota(userpquota) |
---|
157 | if action.startswith("POLICY_") : |
---|
158 | action = action[7:] |
---|
159 | |
---|
160 | if action == "DENY" : |
---|
161 | adminmessage = _("Print Quota exceeded for user %(username)s on printer %(printername)s") % locals() |
---|
162 | self.printInfo(adminmessage) |
---|
163 | if mailto in [ "BOTH", "USER", "EXTERNAL" ] : |
---|
164 | message = self.config.getHardWarn(printername) |
---|
165 | if mailto != "EXTERNAL" : |
---|
166 | self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message) |
---|
167 | else : |
---|
168 | self.externalMailTo(arguments, action, user, printer, message) |
---|
169 | if mailto in [ "BOTH", "ADMIN" ] : |
---|
170 | self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) |
---|
171 | elif action == "WARN" : |
---|
172 | adminmessage = _("Print Quota low for user %(username)s on printer %(printername)s") % locals() |
---|
173 | self.printInfo(adminmessage) |
---|
174 | if mailto in [ "BOTH", "USER", "EXTERNAL" ] : |
---|
175 | if user.LimitBy and (user.LimitBy.lower() == "balance") : |
---|
176 | message = self.config.getPoorWarn() |
---|
177 | else : |
---|
178 | message = self.config.getSoftWarn(printername) |
---|
179 | if mailto != "EXTERNAL" : |
---|
180 | self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message) |
---|
181 | else : |
---|
182 | self.externalMailTo(arguments, action, user, printer, message) |
---|
183 | if mailto in [ "BOTH", "ADMIN" ] : |
---|
184 | self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) |
---|
185 | return action |
---|
186 | |
---|
187 | def main(self, ugnames, options) : |
---|
188 | """Warn users or groups over print quota.""" |
---|
189 | if self.config.isAdmin : |
---|
190 | # PyKota administrator |
---|
191 | if not ugnames : |
---|
192 | # no username, means all usernames |
---|
193 | ugnames = [ "*" ] |
---|
194 | else : |
---|
195 | # not a PyKota administrator |
---|
196 | # warns only the current user |
---|
197 | # the utility of this is discutable, but at least it |
---|
198 | # protects other users from mail bombing if they are |
---|
199 | # over quota. |
---|
200 | username = pwd.getpwuid(os.geteuid())[0].decode("ANSI_X3.4-1968", "replace") |
---|
201 | if options.groups : |
---|
202 | user = self.storage.getUser(username) |
---|
203 | if user.Exists : |
---|
204 | ugnames = [ g.Name for g in self.storage.getUserGroups(user) ] |
---|
205 | else : |
---|
206 | ugnames = [ ] |
---|
207 | else : |
---|
208 | ugnames = [ username ] |
---|
209 | |
---|
210 | printername = options.printer |
---|
211 | printers = self.storage.getMatchingPrinters(printername) |
---|
212 | if not printers : |
---|
213 | raise PyKotaCommandLineError, _("There's no printer matching %(printername)s") \ |
---|
214 | % locals() |
---|
215 | alreadydone = {} |
---|
216 | for printer in printers : |
---|
217 | if options.groups : |
---|
218 | for (group, grouppquota) in self.storage.getPrinterGroupsAndQuotas(printer, ugnames) : |
---|
219 | self.warnGroupPQuota(grouppquota) |
---|
220 | else : |
---|
221 | for (user, userpquota) in self.storage.getPrinterUsersAndQuotas(printer, ugnames) : |
---|
222 | # we only want to warn users who have ever printed something |
---|
223 | # and don't want to warn users who have never printed |
---|
224 | if ((user.AccountBalance > self.config.getBalanceZero()) and \ |
---|
225 | (user.AccountBalance != user.LifeTimePaid)) or \ |
---|
226 | userpquota.PageCounter or userpquota.LifePageCounter or \ |
---|
227 | self.storage.getUserNbJobsFromHistory(user) : |
---|
228 | done = alreadydone.get(user.Name) |
---|
229 | if (user.LimitBy == 'quota') or not done : |
---|
230 | action = self.warnUserPQuota(userpquota) |
---|
231 | if not done : |
---|
232 | alreadydone[user.Name] = (action in ('WARN', 'DENY')) |
---|
233 | |
---|
234 | if __name__ == "__main__" : |
---|
235 | parser = PyKotaOptionParser(description=_("A tool to warn users and groups who have reached the limit of their printing quota."), |
---|
236 | usage="warnpykota [options] [usernames|groupnames]") |
---|
237 | parser.add_option("-g", "--groups", |
---|
238 | action="store_true", |
---|
239 | dest="groups", |
---|
240 | help=_("Notify all members for all the named groups which have reached the limit of their printing quota. Without this option, individual users are notified instead of users groups.")) |
---|
241 | parser.add_option("-P", "--printer", |
---|
242 | dest="printer", |
---|
243 | default="*", |
---|
244 | help=_("Acts on this printer only. You can specify several printer names by separating them with commas. The default value is '%default', which means all printers.")) |
---|
245 | |
---|
246 | parser.add_example('', |
---|
247 | _("This would notify all users who have reached the limit of their printing quota on any printer.")) |
---|
248 | parser.add_example('--printer HP2100', |
---|
249 | _("This would notify all users who have reached the limit of their printing quota on printer 'HP2100'.")) |
---|
250 | parser.add_example('--groups --printer "HP*,XER*" "dev*"', |
---|
251 | _("This would notify all users of the groups whose name begins with 'dev' and for which the printing quota limit is reached on any printer whose name begins with 'HP' or 'XER'.")) |
---|
252 | |
---|
253 | run(parser, WarnPyKota) |
---|