Marius Cramer
2015-08-06 37b29231e47a0c4458dc1c15d98588f16f07e1e2
commit | author | age
5de2af 1 <?php
M 2
3 /*
4 Copyright (c) 2013, Marius Cramer, pixcept KG
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 class cronjob_mailbox_stats extends cronjob {
32
b1a6a5 33     // job schedule
MC 34     protected $_schedule = '0 0 * * *';
024e13 35     protected $mailbox_traffic = array();
MC 36     protected $mail_boxes = array();
37     protected $mail_rewrites = array();
5de2af 38
b1a6a5 39     /* this function is optional if it contains no custom code */
MC 40     public function onPrepare() {
41         global $app;
5de2af 42
b1a6a5 43         parent::onPrepare();
MC 44     }
5de2af 45
b1a6a5 46     /* this function is optional if it contains no custom code */
MC 47     public function onBeforeRun() {
48         global $app;
5de2af 49
b1a6a5 50         return parent::onBeforeRun();
MC 51     }
5de2af 52
b1a6a5 53     public function onRunJob() {
MC 54         global $app, $conf;
5de2af 55
b1a6a5 56         // cronjob code here
5de2af 57
b1a6a5 58         //######################################################################################################
MC 59         // store the mailbox statistics in the database
60         //######################################################################################################
5de2af 61
b1a6a5 62         $parse_mail_log = false;
cc7a82 63         $sql = "SELECT mailuser_id,maildir FROM mail_user WHERE server_id = ?";
MC 64         $records = $app->db->queryAllRecords($sql, $conf['server_id']);
b1a6a5 65         if(count($records) > 0) $parse_mail_log = true;
5de2af 66
b1a6a5 67         foreach($records as $rec) {
MC 68             if(@is_file($rec['maildir'].'/ispconfig_mailsize')) {
69                 $parse_mail_log = false;
5de2af 70
b1a6a5 71                 // rename file
MC 72                 rename($rec['maildir'].'/ispconfig_mailsize', $rec['maildir'].'/ispconfig_mailsize_save');
5de2af 73
b1a6a5 74                 // Read the file
MC 75                 $lines = file($rec['maildir'].'/ispconfig_mailsize_save');
76                 $mail_traffic = 0;
77                 foreach($lines as $line) {
78                     $mail_traffic += intval($line);
79                 }
80                 unset($lines);
5de2af 81
b1a6a5 82                 // Delete backup file
MC 83                 if(@is_file($rec['maildir'].'/ispconfig_mailsize_save')) unlink($rec['maildir'].'/ispconfig_mailsize_save');
5de2af 84
b1a6a5 85                 // Save the traffic stats in the sql database
MC 86                 $tstamp = date('Y-m');
5de2af 87
cc7a82 88                 $sql = "SELECT * FROM mail_traffic WHERE month = '$tstamp' AND mailuser_id = ?";
MC 89                 $tr = $app->dbmaster->queryOneRecord($sql, $rec['mailuser_id']);
5de2af 90
b1a6a5 91                 $mail_traffic += $tr['traffic'];
MC 92                 if($tr['traffic_id'] > 0) {
cc7a82 93                     $sql = "UPDATE mail_traffic SET traffic = ? WHERE traffic_id = ?";
MC 94                     $app->dbmaster->query($sql, $mail_traffic, $tr['traffic_id']);
b1a6a5 95                 } else {
cc7a82 96                     $sql = "INSERT INTO mail_traffic (month,mailuser_id,traffic) VALUES (?,?,?)";
MC 97                     $app->dbmaster->query($sql, $tstamp, $rec['mailuser_id'], $mail_traffic);
b1a6a5 98                 }
MC 99                 //echo $sql;
5de2af 100
b1a6a5 101             }
5de2af 102
b1a6a5 103         }
5de2af 104
b1a6a5 105         if($parse_mail_log == true) {
MC 106             $mailbox_traffic = array();
107             $mail_boxes = array();
108             $mail_rewrites = array(); // we need to read all mail aliases and forwards because the address in amavis is not always the mailbox address
5de2af 109
b1a6a5 110             function parse_mail_log_line($line) {
MC 111                 //Oct 31 17:35:48 mx01 amavis[32014]: (32014-05) Passed CLEAN, [IPv6:xxxxx] [IPv6:xxxxx] <xxx@yyyy> -> <aaaa@bbbb>, Message-ID: <xxxx@yyyyy>, mail_id: xxxxxx, Hits: -1.89, size: 1591, queued_as: xxxxxxx, 946 ms
5de2af 112
b1a6a5 113                 if(preg_match('/^(\w+\s+\d+\s+\d+:\d+:\d+)\s+[^ ]+\s+amavis.* <([^>]+)>\s+->\s+((<[^>]+>,)+) .*Message-ID:\s+<([^>]+)>.* size:\s+(\d+),.*$/', $line, $matches) == false) return false;
5de2af 114
b1a6a5 115                 $timestamp = strtotime($matches[1]);
MC 116                 if(!$timestamp) return false;
5de2af 117
b1a6a5 118                 $to = array();
MC 119                 $recipients = explode(',', $matches[3]);
120                 foreach($recipients as $recipient) {
121                     $recipient = substr($recipient, 1, -1);
122                     if(!$recipient || $recipient == $matches[2]) continue;
123                     $to[] = $recipient;
124                 }
5de2af 125
b1a6a5 126                 return array('line' => $line, 'timestamp' => $timestamp, 'size' => $matches[6], 'from' => $matches[2], 'to' => $to, 'message-id' => $matches[5]);
MC 127             }
5de2af 128
b1a6a5 129             function add_mailbox_traffic(&$traffic_array, $address, $traffic) {
MC 130                 global $mail_boxes, $mail_rewrites;
5de2af 131
b1a6a5 132                 $address = strtolower($address);
5de2af 133
b1a6a5 134                 if(in_array($address, $mail_boxes) == true) {
MC 135                     if(!isset($traffic_array[$address])) $traffic_array[$address] = 0;
136                     $traffic_array[$address] += $traffic;
137                 } elseif(array_key_exists($address, $mail_rewrites)) {
138                     foreach($mail_rewrites[$address] as $address) {
139                         if(!isset($traffic_array[$address])) $traffic_array[$address] = 0;
140                         $traffic_array[$address] += $traffic;
141                     }
142                 } else {
143                     // this is not a local address - skip it
144                 }
145             }
5de2af 146
cc7a82 147             $sql = "SELECT email FROM mail_user WHERE server_id = ?";
MC 148             $records = $app->db->queryAllRecords($sql, $conf['server_id']);
b1a6a5 149             foreach($records as $record) {
MC 150                 $mail_boxes[] = $record['email'];
151             }
cc7a82 152             $sql = "SELECT source, destination FROM mail_forwarding WHERE server_id = ?";
MC 153             $records = $app->db->queryAllRecords($sql, $conf['server_id']);
b1a6a5 154             foreach($records as $record) {
MC 155                 $targets = preg_split('/[\n,]+/', $record['destination']);
156                 foreach($targets as $target) {
157                     if(in_array($target, $mail_boxes)) {
158                         if(isset($mail_rewrites[$record['source']])) $mail_rewrites[$record['source']][] = $target;
159                         else $mail_rewrites[$record['source']] = array($target);
160                     }
161                 }
162             }
5de2af 163
b1a6a5 164             $state_file = dirname(__FILE__) . '/mail_log_parser.state';
MC 165             $prev_line = false;
166             $last_line = false;
167             $cur_line = false;
5de2af 168
b1a6a5 169             if(file_exists($state_file)) {
024e13 170                 $prev_line = $this->parse_mail_log_line(trim(file_get_contents($state_file)));
b1a6a5 171                 //if($prev_line) echo "continuing from previous run, log position: " . $prev_line['message-id'] . " at " . strftime('%d.%m.%Y %H:%M:%S', $prev_line['timestamp']) . "\n";
MC 172             }
5de2af 173
b1a6a5 174             if(file_exists('/var/log/mail.log')) {
MC 175                 $fp = fopen('/var/log/mail.log', 'r');
176                 //echo "Parsing mail.log...\n";
177                 $l = 0;
178                 while($line = fgets($fp, 8192)) {
179                     $l++;
180                     //if($l % 1000 == 0) echo "\rline $l";
024e13 181                     $cur_line = $this->parse_mail_log_line($line);
MC 182                     //print_r($cur_line);
b1a6a5 183                     if(!$cur_line) continue;
MC 184
185                     if($prev_line) {
186                         // check if this line has to be processed
187                         if($cur_line['timestamp'] < $prev_line['timestamp']) {
188                             $parse_mail_log = false; // we do not need to parse the second file!
189                             continue; // already processed
190                         } elseif($cur_line['timestamp'] == $prev_line['timestamp'] && $cur_line['message-id'] == $prev_line['message-id']) {
191                             $parse_mail_log = false; // we do not need to parse the second file!
192                             $prev_line = false; // this line has already been processed but the next one has to be!
193                             continue;
194                         }
195                     }
196
024e13 197                     $this->add_mailbox_traffic($cur_line['from'], $cur_line['size']);
MC 198                     //echo "1\n";
199                     //print_r($this->mailbox_traffic);
b1a6a5 200                     foreach($cur_line['to'] as $to) {
024e13 201                         $this->add_mailbox_traffic($to, $cur_line['size']);
MC 202                         //echo "2\n";
203                         //print_r($this->mailbox_traffic);
b1a6a5 204                     }
MC 205                     $last_line = $line; // store for the state file
206                 }
207                 fclose($fp);
208                 //echo "\n";
209             }
210
211             if($parse_mail_log == true && file_exists('/var/log/mail.log.1')) {
212                 $fp = fopen('/var/log/mail.log.1', 'r');
213                 //echo "Parsing mail.log.1...\n";
214                 $l = 0;
215                 while($line = fgets($fp, 8192)) {
216                     $l++;
217                     //if($l % 1000 == 0) echo "\rline $l";
024e13 218                     $cur_line = $this->parse_mail_log_line($line);
b1a6a5 219                     if(!$cur_line) continue;
MC 220
221                     if($prev_line) {
222                         // check if this line has to be processed
223                         if($cur_line['timestamp'] < $prev_line['timestamp']) continue; // already processed
224                         if($cur_line['timestamp'] == $prev_line['timestamp'] && $cur_line['message-id'] == $prev_line['message-id']) {
225                             $prev_line = false; // this line has already been processed but the next one has to be!
226                             continue;
227                         }
228                     }
229
230                     add_mailbox_traffic($mailbox_traffic, $cur_line['from'], $cur_line['size']);
231                     foreach($cur_line['to'] as $to) {
232                         add_mailbox_traffic($mailbox_traffic, $to, $cur_line['size']);
233                     }
234                 }
235                 fclose($fp);
236                 //echo "\n";
237             }
238             unset($mail_rewrites);
239             unset($mail_boxes);
240
241             // Save the traffic stats in the sql database
242             $tstamp = date('Y-m');
cc7a82 243             $sql = "SELECT mailuser_id,email FROM mail_user WHERE server_id = ?";
MC 244             $records = $app->db->queryAllRecords($sql, $conf['server_id']);
b1a6a5 245             foreach($records as $rec) {
MC 246                 if(array_key_exists($rec['email'], $mailbox_traffic)) {
cc7a82 247                     $sql = "SELECT * FROM mail_traffic WHERE month = ? AND mailuser_id = ?";
MC 248                     $tr = $app->dbmaster->queryOneRecord($sql, $tstamp, $rec['mailuser_id']);
b1a6a5 249
MC 250                     $mail_traffic = $tr['traffic'] + $mailbox_traffic[$rec['email']];
251                     if($tr['traffic_id'] > 0) {
cc7a82 252                         $sql = "UPDATE mail_traffic SET traffic = ? WHERE traffic_id = ?";
MC 253                         $app->dbmaster->query($sql, $mail_traffic, $tr['traffic_id']);
b1a6a5 254                     } else {
cc7a82 255                         $sql = "INSERT INTO mail_traffic (month,mailuser_id,traffic) VALUES (?,?,?)";
MC 256                         $app->dbmaster->query($sql, $tstamp, $rec['mailuser_id'], $mail_traffic);
b1a6a5 257                     }
MC 258                     //echo $sql;
259                 }
260             }
261
262             unset($mailbox_traffic);
263             if($last_line) file_put_contents($state_file, $last_line);
264         }
265
266
267         parent::onRunJob();
268     }
269
270     /* this function is optional if it contains no custom code */
271     public function onAfterRun() {
272         global $app;
273
274         parent::onAfterRun();
275     }
024e13 276     
MC 277     private function parse_mail_log_line($line) {
278         //Oct 31 17:35:48 mx01 amavis[32014]: (32014-05) Passed CLEAN, [IPv6:xxxxx] [IPv6:xxxxx] <xxx@yyyy> -> <aaaa@bbbb>, Message-ID: <xxxx@yyyyy>, mail_id: xxxxxx, Hits: -1.89, size: 1591, queued_as: xxxxxxx, 946 ms
279
280         if(preg_match('/^(\w+\s+\d+\s+\d+:\d+:\d+)\s+[^ ]+\s+amavis.* <([^>]+)>\s+->\s+((<[^>]+>,)+) .*Message-ID:\s+<([^>]+)>.* size:\s+(\d+),.*$/', $line, $matches) == false) return false;
281
282         $timestamp = strtotime($matches[1]);
283         if(!$timestamp) return false;
284
285         $to = array();
286         $recipients = explode(',', $matches[3]);
287         foreach($recipients as $recipient) {
288             $recipient = substr($recipient, 1, -1);
289             if(!$recipient || $recipient == $matches[2]) continue;
290             $to[] = $recipient;
291         }
292         return array('line' => $line, 'timestamp' => $timestamp, 'size' => $matches[6], 'from' => $matches[2], 'to' => $to, 'message-id' => $matches[5]);
293     }
294     
295     private function add_mailbox_traffic($address, $traffic) {
296
297         $address = strtolower($address);
298
299         if(in_array($address, $this->mail_boxes) == true) {
300             if(!isset($this->mailbox_traffic[$address])) $this->mailbox_traffic[$address] = 0;
301             $this->mailbox_traffic[$address] += $traffic;
302         } elseif(array_key_exists($address, $this->mail_rewrites)) {
303             foreach($this->mail_rewrites[$address] as $address) {
304                 if(!isset($this->mailbox_traffic[$address])) $this->mailbox_traffic[$address] = 0;
305                 $this->mailbox_traffic[$address] += $traffic;
306             }
307         } else {
308             // this is not a local address - skip it
309         }
310     }
5de2af 311
M 312 }
313
314 ?>