Index: /pykota/trunk/conf/pykota.conf.sample
===================================================================
--- /pykota/trunk/conf/pykota.conf.sample (revision 2445)
+++ /pykota/trunk/conf/pykota.conf.sample (revision 2474)
@@ -758,4 +758,6 @@
 # PYKOTAOVERCHARGE : user's overcharging factor.
 # PYKOTAJOBBILLING : Job's billing code if present (CUPS only)
+# PYKOTAREASON : if the job was denied or a warning needs to be issued, contains
+#                the message to send to the user.
 #
 
Index: /pykota/trunk/pykota/version.py
===================================================================
--- /pykota/trunk/pykota/version.py (revision 2465)
+++ /pykota/trunk/pykota/version.py (revision 2474)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.23alpha31_unofficial"
+__version__ = "1.23beta_unofficial"
 
 __doc__ = "PyKota : a complete Printing Quota Solution for CUPS and LPRng."
Index: /pykota/trunk/bin/cupspykota
===================================================================
--- /pykota/trunk/bin/cupspykota (revision 2463)
+++ /pykota/trunk/bin/cupspykota (revision 2474)
@@ -36,4 +36,6 @@
 import fnmatch
 import pwd
+import socket
+import smtplib
 
 from pykota.tool import PyKotaTool, PyKotaToolError, crashed
@@ -188,4 +190,5 @@
         
         self.Action = "ALLOW"   # job allowed by default
+        self.Reason = None
         self.JobId = sys.argv[1].strip()
         # use CUPS' user when printing test pages from CUPS' web interface
@@ -292,4 +295,5 @@
             if action :
                 self.Action = action
+                self.Reason = _("You are not allowed to print at this time.")
             if username :
                 self.UserName = username
@@ -517,4 +521,12 @@
         os.environ["PYKOTAJOBSIZE"] = str(self.JobSize)
         os.environ["PYKOTAJOBPRICE"] = str(self.JobPrice)
+        self.logdebug("Environment updated.")
+        
+    def exportReason(self) :
+        """Exports the job's action status and optional reason."""
+        self.logdebug("Exporting job's action status...")
+        os.environ["PYKOTAACTION"] = str(self.Action)
+        if self.Reason :
+            os.environ["PYKOTAREASON"] = str(self.Reason)
         self.logdebug("Environment updated.")
         
@@ -768,4 +780,5 @@
                 self.printInfo("%s : %s." % (msg, _("Printing is denied by configuration")), "warn")
                 self.Action = "DENY"
+                self.Reason = _("Duplicate print jobs are not allowed on printer %s.") % self.PrinterName
             else :    
                 self.logdebug("Launching subprocess [%s] to see if dupes should be allowed or not." % denyduplicates)
@@ -775,4 +788,5 @@
                 if self.Action == "DENY" :     
                     self.printInfo("%s : %s." % (msg, _("Subprocess denied printing of a dupe")), "warn")
+                    self.Reason = _("Duplicate print jobs are not allowed on printer %s at this time.") % self.PrinterName
                 else :    
                     self.printInfo("%s : %s." % (msg, _("Subprocess allowed printing of a dupe")), "warn")
@@ -781,4 +795,43 @@
         self.logdebug("Checking if the job is a dupe done.")
         
+    def tellUser(self) :
+        """Sends a message to an user."""
+        self.logdebug("Sending some feedback to user %s..." % self.UserName)  
+        if not self.Reason :
+            self.logdebug("No feedback to send to user %s." % self.UserName)
+        else :    
+            (mailto, arguments) = self.config.getMailTo(self.PrinterName)
+            if mailto == "EXTERNAL" :
+                # TODO : clean this again
+                self.externalMailTo(arguments, self.Action, self.User, self.Printer, self.Reason)
+            else :    
+                # TODO : clean this again
+                admin = self.config.getAdmin(self.PrinterName)
+                adminmail = self.config.getAdminMail(self.PrinterName)
+                usermail = self.User.Email or self.User.Name
+                if "@" not in usermail :
+                    usermail = "%s@%s" % (usermail, self.maildomain or self.smtpserver)
+                destination = []
+                if mailto in ("BOTH", "ADMIN") :
+                    destination.append(adminmail)
+                if mailto in ("BOTH", "USER") :    
+                    destination.append(usermail)
+                    
+                fullmessage = self.Reason + (_("\n\nYour system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail))
+                try :    
+                    server = smtplib.SMTP(self.smtpserver)
+                except socket.error, msg :    
+                    self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error")
+                else :
+                    try :
+                        server.sendmail(adminmail, destination, \
+                          "From: %s\nTo: %s\nCc: %s\nSubject: %s\n\n%s" \
+                          % (adminmail, usermail, adminmail, _("Print Quota"), fullmessage))
+                    except smtplib.SMTPException, answer :    
+                        for (k, v) in answer.recipients.items() :
+                            self.printInfo(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
+                    server.quit()
+            self.logdebug("Feedback sent to user %s." % self.UserName)
+                
     def mainWork(self) :    
         """Main work is done here."""
@@ -827,4 +880,7 @@
                 self.printInfo(_("Precomputed job size (%s pages) too large for printer %s.") % (self.softwareJobSize, self.PrinterName), "warn")
                 self.Action = "DENY"
+                # here we don't put the precomputed job size in the message
+                # because in case of error the user could complain :-)
+                self.Reason = _("You are not allowed to print so many pages on printer %s at this time.") % self.PrinterName
             
         if self.Action != "DENY" :
@@ -832,4 +888,5 @@
                 self.printInfo(_("User %s is not allowed to print at this time.") % self.UserName, "warn")
                 self.Action = "DENY"
+                self.Reason = _("Your account settings forbid you to print at this time.")
                 
         if self.Action != "DENY" :
@@ -859,8 +916,22 @@
                 self.logdebug("Checking user %s print quota entry on printer %s" \
                                     % (self.UserName, self.PrinterName))
-                self.Action = self.warnUserPQuota(self.UserPQuota)
+                self.Action = self.checkUserPQuota(self.UserPQuota)
+                if self.Action.startswith("POLICY_") :
+                    self.Action = self.Action[7:]
+                if self.Action == "DENY" :
+                    self.printInfo(_("Print Quota exceeded for user %s on printer %s") % (self.UserName, self.PrinterName))
+                    self.Reason = self.config.getHardWarn(self.PrinterName)
+                elif self.Action == "WARN" :    
+                    self.printInfo(_("Print Quota low for user %s on printer %s") % (self.UserName, self.PrinterName))
+                    if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 
+                        self.Reason = self.config.getPoorWarn()
+                    else :     
+                        self.Reason = self.config.getSoftWarn(self.PrinterName)
             
         # exports some new environment variables
-        os.environ["PYKOTAACTION"] = str(self.Action)
+        self.exportReason()
+        
+        # now tell the user if he needs to know something
+        self.tellUser()
         
         # launches the pre hook
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 2467)
+++ /pykota/trunk/NEWS (revision 2474)
@@ -22,4 +22,14 @@
 PyKota NEWS :
        
+    - 1.23beta :
+    
+        - Now the mailto directive is honored in all cases where the job is
+          denied or a warning has to be issued, and the correct reason
+          can be sent back to the user.
+          
+        - The PYKOTAREASON environment variable, if defined, contains the  
+          textual reason why the job was denied or a warning has to be
+          issued.
+        
     - 1.23alpha31 :
     
