Marius Burkard
2016-04-20 4569cae57f127afd093794310ccd290d2d9fdf36
commit | author | age
ec5716 1 <?php
T 2
b9ea02 3 /**
b1a6a5 4  Copyright (c) 2007 - 2013, Till Brehm, projektfarm Gmbh
MC 5  Copyright (c) 2013, Florian Schaal, info@schaal-24.de
6  All rights reserved.
56f698 7
b1a6a5 8  Redistribution and use in source and binary forms, with or without modification,
MC 9  are permitted provided that the following conditions are met:
56f698 10
b1a6a5 11  * Redistributions of source code must retain the above copyright notice,
MC 12  this list of conditions and the following disclaimer.
13  * Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16  * Neither the name of ISPConfig nor the names of its contributors
17  may be used to endorse or promote products derived from this software without
18  specific prior written permission.
56f698 19
b1a6a5 20  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
MC 21  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
b9ea02 30
b1a6a5 31  @author Florian Schaal, info@schaal-24.de
2c7813 32  @copyright Florian Schaal, info@schaal-24.de
b1a6a5 33  */
MC 34
56f698 35
T 36 class mail_plugin_dkim {
37
38     var $plugin_name = 'mail_plugin_dkim';
39     var $class_name = 'mail_plugin_dkim';
40
41     // private variables
42     var $action = '';
43
b1a6a5 44     /**
MC 45      * This function is called during ispconfig installation to determine
46      * if a symlink shall be created for this plugin.
47      */
56f698 48     function onInstall() {
T 49         global $conf;
50
51         if($conf['services']['mail'] == true) {
52             return true;
53         } else {
54             return false;
55         }
56
57     }
58
b9ea02 59     /**
b1a6a5 60      * This function is called when the plugin is loaded
MC 61      */
56f698 62     function onLoad() {
b1a6a5 63         global $app, $conf;
56f698 64         /*
T 65         Register for the events
66         */
b1a6a5 67         $app->plugins->registerEvent('mail_domain_delete', $this->plugin_name, 'domain_dkim_delete');
MC 68         $app->plugins->registerEvent('mail_domain_insert', $this->plugin_name, 'domain_dkim_insert');
69         $app->plugins->registerEvent('mail_domain_update', $this->plugin_name, 'domain_dkim_update');
ec5716 70     }
b1a6a5 71
MC 72     /**
73      * This function gets the amavisd-config file
74      * @return string path to the amavisd-config for dkim-keys
75      */
ec5716 76     function get_amavis_config() {
T 77         $pos_config=array(
78             '/etc/amavisd.conf/50-user',
03200f 79             '/etc/amavis/conf.d/50-user',
59be1c 80             '/etc/amavisd.conf',
03200f 81             '/etc/amavisd/amavisd.conf'
ec5716 82         );
T 83         $amavis_configfile='';
b1a6a5 84         foreach($pos_config as $conf) {
56f698 85             if (is_file($conf)) {
T 86                 $amavis_configfile=$conf;
87                 break;
88             }
b1a6a5 89         }
e52d51 90         //* If we can use seperate config-files with amavis use 60-dkim
FS 91         if (substr_compare($amavis_configfile, '50-user', -7) === 0)
92             $amavis_configfile = str_replace('50-user', '60-dkim', $amavis_configfile);
93
56f698 94         return $amavis_configfile;
T 95     }
b1a6a5 96
b9ea02 97     /**
b1a6a5 98      * This function checks the relevant configs and disables dkim for the domain
MC 99      * if the directory for dkim is not writeable or does not exist
100      * @param array $data mail-settings
101      * @return boolean - true when the amavis-config and the dkim-dir are writeable
102      */
56f698 103     function check_system($data) {
b1a6a5 104         global $app, $mail_config;
e52d51 105
b1a6a5 106         $app->uses('getconf');
56f698 107         $check=true;
e52d51 108
56f698 109         /* check for amavis-config */
e52d51 110         $amavis_configfile = $this->get_amavis_config();
FS 111
112         //* When we can use 60-dkim for the dkim-keys create the file if it does not exists.
113         if (substr_compare($amavis_configfile, '60-dkim', -7) === 0 && !file_exists($amavis_configfile))
2b4222 114             $app->system->touch($amavis_configfile);
e52d51 115
FS 116         if ( $amavis_configfile == '' || !is_writeable($amavis_configfile) ) {
b1a6a5 117             $app->log('Amavis-config not found or not writeable.', LOGLEVEL_ERROR);
56f698 118             $check=false;
T 119         }
a8739d 120
56f698 121         /* dir for dkim-keys writeable? */
b1a6a5 122         $mail_config = $app->getconf->get_server_config($conf['server_id'], 'mail');
165399 123         if (    isset($mail_config['dkim_path']) && 
FS 124                 !empty($mail_config['dkim_path']) && 
141500 125 //                isset($data['new']['dkim_private']) && 
FS 126 //                !empty($data['new']['dkim_private']) &&
a8739d 127                 $mail_config['dkim_path'] != '/' 
165399 128         ) {
64b2a9 129             if (!is_dir($mail_config['dkim_path'])) {
F 130                 $app->log('DKIM Path '.$mail_config['dkim_path'].' not found - (re)created.', LOGLEVEL_DEBUG);
d5712b 131                 if($app->system->is_user('amavis')) { 
FS 132                     $amavis_user='amavis'; 
133                 } elseif ($app->system->is_user('vscan')) { 
134                     $amavis_user='vscan'; 
135                 }
136                 else { 
137                     $amavis_user=''; 
138                 }
2b05c2 139                 if(!empty($amavis_user)) {
FS 140                     mkdir($mail_config['dkim_path'], 0750, true);
02ff26 141                     $app->system->chown($mail_config['dkim_path'], $amavis_user);
2b05c2 142                 } else {
FS 143                     mkdir($mail_config['dkim_path'], 0755, true);
2b4222 144                     $app->log('No user amavis or vscan found - using root for '.$mail_config['dkim_path'], LOGLEVEL_WARNING);
2b05c2 145                 }
7002a5 146             } else {
FS 147                 if (!$app->system->checkpath($mail_config['dkim_path'])) {
148                     $app->log('Unable to write DKIM settings - invalid DKIM-Path (symlink?)', LOGLEVEL_ERROR);
149                     $check=false;
150                 }
151             }
64b2a9 152
56f698 153             if (!is_writeable($mail_config['dkim_path'])) {
64b2a9 154                 $app->log('DKIM Path '.$mail_config['dkim_path'].' not writeable.', LOGLEVEL_ERROR);
56f698 155                 $check=false;
a8739d 156             }
FS 157
158             if ( !$app->system->checkpath($mail_config['dkim_path']) ) {
159                 $app->log('DKIM Path '.$mail_config['dkim_path'].' failed in checkpath.', LOGLEVEL_ERROR);
160                 $check = false;
b1a6a5 161             }
64b2a9 162
56f698 163         } else {
c9fa7a 164             $app->log('Unable to write DKIM settings - no or invalid DKIM-Path defined', LOGLEVEL_ERROR);
ec5716 165             $check=false;
T 166         }
167         return $check;
168     }
169
b9ea02 170     /**
b1a6a5 171      * This function restarts amavis
MC 172      */
02ff26 173     function restart_amavis() {
FS 174         global $app, $conf;
175         $pos_init=array(
176             $conf['init_scripts'].'/amavis',
177             $conf['init_scripts'].'/amavisd'
178         );
179         $initfile='';
180         foreach($pos_init as $init) {
181             if (is_executable($init)) {
182                 $initfile=$init;
183                 break;
184                 }
185         }
c9fa7a 186         if ( $initfile == '' ) $initfile = 'service amavis';
02ff26 187         $app->log('Restarting amavis: '.$initfile.'.', LOGLEVEL_DEBUG);
FS 188         exec(escapeshellarg($initfile).' restart', $output);
189         foreach($output as $logline) $app->log($logline, LOGLEVEL_DEBUG);
190     }
b1a6a5 191
MC 192     /**
193      * This function writes the keyfiles (public and private)
194      * @param string $key_file full path to the key-file
195      * @param string $key_value private-key
196      * @param string $key_domain mail-domain
a8739d 197      * @return bool - true when the private key was written to disk
b1a6a5 198      */
MC 199     function write_dkim_key($key_file, $key_value, $key_domain) {
200         global $app, $mailconfig;
56f698 201         $success=false;
141500 202         if ($key_file == '' || $key_value  == '' || $key_domain == '') {
FS 203             $app->log('DKIM internal error for domain '.$key_domain, LOGLEVEL_ERROR);
204             return $success;
205         }
c9fa7a 206         if ( $app->system->file_put_contents($key_file.'.private', $key_value) ) {
b1a6a5 207             $app->log('Saved DKIM Private-key to '.$key_file.'.private', LOGLEVEL_DEBUG);
ec5716 208             $success=true;
T 209             /* now we get the DKIM Public-key */
4c794a 210             exec('cat '.escapeshellarg($key_file.'.private').'|openssl rsa -pubout 2> /dev/null', $pubkey, $result);
ec5716 211             $public_key='';
T 212             foreach($pubkey as $values) $public_key=$public_key.$values."\n";
213             /* save the DKIM Public-key in dkim-dir */
c9fa7a 214             if ( $app->system->file_put_contents($key_file.'.public', $public_key) )
b1a6a5 215                 $app->log('Saved DKIM Public to '.$key_domain.'.', LOGLEVEL_DEBUG);
64b2a9 216             else $app->log('Unable to save DKIM Public to '.$key_domain.'.', LOGLEVEL_DEBUG);
165399 217         } else {
141500 218             $app->log('Unable to save DKIM Private-key to '.$key_file.'.private', LOGLEVEL_ERROR);
b1a6a5 219         }
ec5716 220         return $success;
T 221     }
56f698 222
b9ea02 223     /**
b1a6a5 224      * This function removes the keyfiles
MC 225      * @param string $key_file full path to the key-file
226      * @param string $key_domain mail-domain
227      */
228     function remove_dkim_key($key_file, $key_domain) {
ec5716 229         global $app;
T 230         if (file_exists($key_file.'.private')) {
02ff26 231             $app->system->unlink($key_file.'.private');
b1a6a5 232             $app->log('Deleted the DKIM Private-key for '.$key_domain.'.', LOGLEVEL_DEBUG);
MC 233         } else $app->log('Unable to delete the DKIM Private-key for '.$key_domain.' (not found).', LOGLEVEL_DEBUG);
ec5716 234         if (file_exists($key_file.'.public')) {
02ff26 235             $app->system->unlink($key_file.'.public');
b1a6a5 236             $app->log('Deleted the DKIM Public-key for '.$key_domain.'.', LOGLEVEL_DEBUG);
MC 237         } else $app->log('Unable to delete the DKIM Public-key for '.$key_domain.' (not found).', LOGLEVEL_DEBUG);
ec5716 238     }
56f698 239
b9ea02 240     /**
b1a6a5 241      * This function adds the entry to the amavisd-config
MC 242      * @param string $key_domain mail-domain
243      */
76be2b 244     function add_to_amavis($key_domain, $selector, $old_selector) {
b1a6a5 245         global $app, $mail_config;
7805e1 246
69f5c9 247         if (empty($selector)) $selector = 'default';
7805e1 248         $restart = false;
e52d51 249         $amavis_configfile = $this->get_amavis_config();
69f5c9 250
FS 251         $search_regex = "/(\n|\r)?dkim_key\(\'".$key_domain."\',\ \'(".$selector."|".$old_selector."){1}?\'.*/";
e52d51 252
FS 253         //* If we are using seperate config-files with amavis remove existing keys from 50-user to avoid duplicate keys
254         if (substr_compare($amavis_configfile, '60-dkim', -7) === 0) {
255             $temp_configfile = str_replace('60-dkim', '50-user', $amavis_configfile);
2b4222 256             $temp_config = $app->system->file_get_contents($temp_configfile, true);
69f5c9 257             if (preg_match($search_regex, $temp_config)) {
FS 258                 $temp_config = preg_replace($search_regex, '', $temp_config)."\n";
2b4222 259                 $app->system->file_put_contents($temp_configfile, $temp_config, true);
e52d51 260             }
FS 261             unset($temp_configfile);
262             unset($temp_config);
263         }
264
7805e1 265         $key_value="dkim_key('".$key_domain."', '".$selector."', '".$mail_config['dkim_path']."/".$key_domain.".private');\n";
2b4222 266         $amavis_config = $app->system->file_get_contents($amavis_configfile, true);
69f5c9 267         $amavis_config = preg_replace($search_regex, '', $amavis_config).$key_value;
7805e1 268
2b4222 269         if ( $app->system->file_put_contents($amavis_configfile, $amavis_config, true) ) {
7805e1 270             $app->log('Adding DKIM Private-key to amavis-config.', LOGLEVEL_DEBUG);
FS 271             $restart = true;
c6d29c 272         } else {
7805e1 273             $app->log('Unable to add DKIM Private-key for '.$key_domain.' to amavis-config.', LOGLEVEL_ERROR);
56f698 274         }
7805e1 275
FS 276         return $restart;
56f698 277     }
b1a6a5 278
b9ea02 279     /**
b1a6a5 280      * This function removes the entry from the amavisd-config
MC 281      * @param string $key_domain mail-domain
282      */
56f698 283     function remove_from_amavis($key_domain) {
T 284         global $app;
7805e1 285
FS 286         $restart = false;
e52d51 287         $amavis_configfile = $this->get_amavis_config();
2b4222 288         $amavis_config = $app->system->file_get_contents($amavis_configfile, true);
7805e1 289
69f5c9 290         $search_regex = "/(\n|\r)?dkim_key.*".$key_domain.".*(\n|\r)?/";
FS 291
292         if (preg_match($search_regex, $amavis_config)) {
293             $amavis_config = preg_replace($search_regex, '', $amavis_config);
2b4222 294             $app->system->file_put_contents($amavis_configfile, $amavis_config, true);
b1a6a5 295             $app->log('Deleted the DKIM settings from amavis-config for '.$key_domain.'.', LOGLEVEL_DEBUG);
7805e1 296             $restart = true;
FS 297         }
298
e52d51 299         //* If we are using seperate config-files with amavis remove existing keys from 50-user, too
FS 300         if (substr_compare($amavis_configfile, '60-dkim', -7) === 0) {
301             $temp_configfile = str_replace('60-dkim', '50-user', $amavis_configfile);
2b4222 302             $temp_config = $app->system->file_get_contents($temp_configfile, true);
69f5c9 303             if (preg_match($search_regex, $temp_config)) {
FS 304                 $temp_config = preg_replace($search_regex, '', $temp_config);
2b4222 305                 $app->system->file_put_contents($temp_configfile, $temp_config, true);
e52d51 306                 $restart = true;
FS 307             }
308             unset($temp_configfile);
309             unset($temp_config);
310         }
311
7805e1 312         return $restart;
56f698 313     }
T 314
b9ea02 315     /**
b1a6a5 316      * This function controlls new key-files and amavisd-entries
MC 317      * @param array $data mail-settings
318      */
56f698 319     function add_dkim($data) {
T 320         global $app;
b9ea02 321         if ($data['new']['active'] == 'y') {
b1a6a5 322             $mail_config = $app->getconf->get_server_config($conf['server_id'], 'mail');
MC 323             if ( substr($mail_config['dkim_path'], strlen($mail_config['dkim_path'])-1) == '/' )
324                 $mail_config['dkim_path'] = substr($mail_config['dkim_path'], 0, strlen($mail_config['dkim_path'])-1);
325             if ($this->write_dkim_key($mail_config['dkim_path']."/".$data['new']['domain'], $data['new']['dkim_private'], $data['new']['domain'])) {
76be2b 326                 if ($this->add_to_amavis($data['new']['domain'], $data['new']['dkim_selector'], $data['old']['dkim_selector'] )) {
7805e1 327                     $this->restart_amavis();
FS 328                 } else {
329                     $this->remove_dkim_key($mail_config['dkim_path']."/".$data['new']['domain'], $data['new']['domain']);
330                 }
b9ea02 331             } else {
8d3466 332                 $app->log('Error saving the DKIM Private-key for '.$data['new']['domain'].' - DKIM is not enabled for the domain.', LOGLEVEL_DEBUG);
b9ea02 333             }
ec5716 334         }
T 335     }
336
b9ea02 337     /**
b1a6a5 338      * This function controlls the removement of keyfiles (public and private)
MC 339      * and the entry in the amavisd-config
340      * @param array $data mail-settings
341      */
342     function remove_dkim($_data) {
343         global $app;
344         $mail_config = $app->getconf->get_server_config($conf['server_id'], 'mail');
345         if ( substr($mail_config['dkim_path'], strlen($mail_config['dkim_path'])-1) == '/' )
346             $mail_config['dkim_path'] = substr($mail_config['dkim_path'], 0, strlen($mail_config['dkim_path'])-1);
347         $this->remove_dkim_key($mail_config['dkim_path']."/".$_data['domain'], $_data['domain']);
7805e1 348         if ($this->remove_from_amavis($_data['domain']))
FS 349             $this->restart_amavis();
b1a6a5 350     }
MC 351
352     /**
353      * Function called by onLoad
354      * deletes dkim-keys
355      */
356     function domain_dkim_delete($event_name, $data) {
7805e1 357         if (isset($data['old']['dkim']) && $data['old']['dkim'] == 'y' && $data['old']['active'] == 'y')
FS 358             $this->remove_dkim($data['old']);
56f698 359     }
b1a6a5 360
b9ea02 361     /**
b1a6a5 362      * Function called by onLoad
MC 363      * insert dkim-keys
364      */
365     function domain_dkim_insert($event_name, $data) {
7805e1 366         if (isset($data['new']['dkim']) && $data['new']['dkim']=='y' && $this->check_system($data))
ec5716 367             $this->add_dkim($data);
T 368     }
56f698 369
b9ea02 370     /**
b1a6a5 371      * Function called by onLoad
MC 372      * chang dkim-settings
373      */
374     function domain_dkim_update($event_name, $data) {
56f698 375         global $app;
8d3466 376         if($data['new']['dkim'] == 'y' || $data['old']['dkim'] == 'y'){
MC 377             if ($this->check_system($data)) {
378                 /* maildomain disabled */
379                 if ($data['new']['active'] == 'n' && $data['old']['active'] == 'y' && $data['new']['dkim']=='y') {
380                     $app->log('Maildomain '.$data['new']['domain'].' disabled - remove DKIM-settings', LOGLEVEL_DEBUG);
c6d29c 381                     $this->remove_dkim($data['new']);
FS 382                 }
8d3466 383                 /* maildomain re-enabled */
MC 384                 if ($data['new']['active'] == 'y' && $data['old']['active'] == 'n' && $data['new']['dkim']=='y') 
c6d29c 385                     $this->add_dkim($data);
FS 386
8d3466 387                 /* maildomain active - only dkim changes */
MC 388                 if ($data['new']['active'] == 'y' && $data['old']['active'] == 'y') {
389                     /* dkim disabled */
390                     if ($data['new']['dkim'] != $data['old']['dkim'] && $data['new']['dkim'] == 'n') {
391                         $this->remove_dkim($data['new']);
392                     }
393                     /* dkim enabled */
394                     elseif ($data['new']['dkim'] != $data['old']['dkim'] && $data['new']['dkim'] == 'y') {
395                         $this->add_dkim($data);
396                     }
397                     /* new private-key */
398                     if ($data['new']['dkim_private'] != $data['old']['dkim_private'] && $data['new']['dkim'] == 'y') {
399                         $this->add_dkim($data);
400                     }
401                     /* new selector */
402                     if ($data['new']['dkim_selector'] != $data['old']['dkim_selector'] && $data['new']['dkim'] == 'y') {
403                         $this->add_dkim($data);
404                     }
405                     /* new domain-name */
406                     if ($data['new']['domain'] != $data['old']['domain']) {
407                         $this->remove_dkim($data['old']);
408                         $this->add_dkim($data);
409                     }
410                 }
411
412                 /* resync */
413                 if ($data['new']['active'] == 'y' && $data['new'] == $data['old'] && $data['new']['dkim']=='y') {
414                     $this->add_dkim($data);
415                 }
b1a6a5 416             }
56f698 417         }
T 418     }
b1a6a5 419
56f698 420 }
b1a6a5 421
56f698 422 ?>