Till Brehm
2016-07-24 b9a3ef486ebcde18a5ade37865ff8f397185d24f
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() {
ea476a 174         global $app;
FS 175         $initcommand = $app->system->getinitcommand(array('amavis', 'amavisd'), 'restart');
176         $app->log('Restarting amavis: '.$initcommand.'.', LOGLEVEL_DEBUG);
177         exec($initcommand, $output);
178         foreach($output as $logline) $app->log($logline, LOGLEVEL_DEBUG);
02ff26 179     }
b1a6a5 180
MC 181     /**
182      * This function writes the keyfiles (public and private)
183      * @param string $key_file full path to the key-file
184      * @param string $key_value private-key
185      * @param string $key_domain mail-domain
a8739d 186      * @return bool - true when the private key was written to disk
b1a6a5 187      */
MC 188     function write_dkim_key($key_file, $key_value, $key_domain) {
189         global $app, $mailconfig;
56f698 190         $success=false;
141500 191         if ($key_file == '' || $key_value  == '' || $key_domain == '') {
FS 192             $app->log('DKIM internal error for domain '.$key_domain, LOGLEVEL_ERROR);
193             return $success;
194         }
c9fa7a 195         if ( $app->system->file_put_contents($key_file.'.private', $key_value) ) {
b1a6a5 196             $app->log('Saved DKIM Private-key to '.$key_file.'.private', LOGLEVEL_DEBUG);
ec5716 197             $success=true;
T 198             /* now we get the DKIM Public-key */
4c794a 199             exec('cat '.escapeshellarg($key_file.'.private').'|openssl rsa -pubout 2> /dev/null', $pubkey, $result);
ec5716 200             $public_key='';
T 201             foreach($pubkey as $values) $public_key=$public_key.$values."\n";
202             /* save the DKIM Public-key in dkim-dir */
c9fa7a 203             if ( $app->system->file_put_contents($key_file.'.public', $public_key) )
b1a6a5 204                 $app->log('Saved DKIM Public to '.$key_domain.'.', LOGLEVEL_DEBUG);
64b2a9 205             else $app->log('Unable to save DKIM Public to '.$key_domain.'.', LOGLEVEL_DEBUG);
165399 206         } else {
141500 207             $app->log('Unable to save DKIM Private-key to '.$key_file.'.private', LOGLEVEL_ERROR);
b1a6a5 208         }
ec5716 209         return $success;
T 210     }
56f698 211
b9ea02 212     /**
b1a6a5 213      * This function removes the keyfiles
MC 214      * @param string $key_file full path to the key-file
215      * @param string $key_domain mail-domain
216      */
217     function remove_dkim_key($key_file, $key_domain) {
ec5716 218         global $app;
T 219         if (file_exists($key_file.'.private')) {
02ff26 220             $app->system->unlink($key_file.'.private');
b1a6a5 221             $app->log('Deleted the DKIM Private-key for '.$key_domain.'.', LOGLEVEL_DEBUG);
MC 222         } else $app->log('Unable to delete the DKIM Private-key for '.$key_domain.' (not found).', LOGLEVEL_DEBUG);
ec5716 223         if (file_exists($key_file.'.public')) {
02ff26 224             $app->system->unlink($key_file.'.public');
b1a6a5 225             $app->log('Deleted the DKIM Public-key for '.$key_domain.'.', LOGLEVEL_DEBUG);
MC 226         } else $app->log('Unable to delete the DKIM Public-key for '.$key_domain.' (not found).', LOGLEVEL_DEBUG);
ec5716 227     }
56f698 228
b9ea02 229     /**
b1a6a5 230      * This function adds the entry to the amavisd-config
MC 231      * @param string $key_domain mail-domain
232      */
76be2b 233     function add_to_amavis($key_domain, $selector, $old_selector) {
b1a6a5 234         global $app, $mail_config;
7805e1 235
69f5c9 236         if (empty($selector)) $selector = 'default';
7805e1 237         $restart = false;
e52d51 238         $amavis_configfile = $this->get_amavis_config();
69f5c9 239
FS 240         $search_regex = "/(\n|\r)?dkim_key\(\'".$key_domain."\',\ \'(".$selector."|".$old_selector."){1}?\'.*/";
e52d51 241
FS 242         //* If we are using seperate config-files with amavis remove existing keys from 50-user to avoid duplicate keys
243         if (substr_compare($amavis_configfile, '60-dkim', -7) === 0) {
244             $temp_configfile = str_replace('60-dkim', '50-user', $amavis_configfile);
2b4222 245             $temp_config = $app->system->file_get_contents($temp_configfile, true);
69f5c9 246             if (preg_match($search_regex, $temp_config)) {
FS 247                 $temp_config = preg_replace($search_regex, '', $temp_config)."\n";
2b4222 248                 $app->system->file_put_contents($temp_configfile, $temp_config, true);
e52d51 249             }
FS 250             unset($temp_configfile);
251             unset($temp_config);
252         }
253
7805e1 254         $key_value="dkim_key('".$key_domain."', '".$selector."', '".$mail_config['dkim_path']."/".$key_domain.".private');\n";
2b4222 255         $amavis_config = $app->system->file_get_contents($amavis_configfile, true);
69f5c9 256         $amavis_config = preg_replace($search_regex, '', $amavis_config).$key_value;
7805e1 257
2b4222 258         if ( $app->system->file_put_contents($amavis_configfile, $amavis_config, true) ) {
7805e1 259             $app->log('Adding DKIM Private-key to amavis-config.', LOGLEVEL_DEBUG);
FS 260             $restart = true;
c6d29c 261         } else {
7805e1 262             $app->log('Unable to add DKIM Private-key for '.$key_domain.' to amavis-config.', LOGLEVEL_ERROR);
56f698 263         }
7805e1 264
FS 265         return $restart;
56f698 266     }
b1a6a5 267
b9ea02 268     /**
b1a6a5 269      * This function removes the entry from the amavisd-config
MC 270      * @param string $key_domain mail-domain
271      */
56f698 272     function remove_from_amavis($key_domain) {
T 273         global $app;
7805e1 274
FS 275         $restart = false;
e52d51 276         $amavis_configfile = $this->get_amavis_config();
2b4222 277         $amavis_config = $app->system->file_get_contents($amavis_configfile, true);
7805e1 278
69f5c9 279         $search_regex = "/(\n|\r)?dkim_key.*".$key_domain.".*(\n|\r)?/";
FS 280
281         if (preg_match($search_regex, $amavis_config)) {
282             $amavis_config = preg_replace($search_regex, '', $amavis_config);
2b4222 283             $app->system->file_put_contents($amavis_configfile, $amavis_config, true);
b1a6a5 284             $app->log('Deleted the DKIM settings from amavis-config for '.$key_domain.'.', LOGLEVEL_DEBUG);
7805e1 285             $restart = true;
FS 286         }
287
e52d51 288         //* If we are using seperate config-files with amavis remove existing keys from 50-user, too
FS 289         if (substr_compare($amavis_configfile, '60-dkim', -7) === 0) {
290             $temp_configfile = str_replace('60-dkim', '50-user', $amavis_configfile);
2b4222 291             $temp_config = $app->system->file_get_contents($temp_configfile, true);
69f5c9 292             if (preg_match($search_regex, $temp_config)) {
FS 293                 $temp_config = preg_replace($search_regex, '', $temp_config);
2b4222 294                 $app->system->file_put_contents($temp_configfile, $temp_config, true);
e52d51 295                 $restart = true;
FS 296             }
297             unset($temp_configfile);
298             unset($temp_config);
299         }
300
7805e1 301         return $restart;
56f698 302     }
T 303
b9ea02 304     /**
b1a6a5 305      * This function controlls new key-files and amavisd-entries
MC 306      * @param array $data mail-settings
307      */
56f698 308     function add_dkim($data) {
T 309         global $app;
b9ea02 310         if ($data['new']['active'] == 'y') {
b1a6a5 311             $mail_config = $app->getconf->get_server_config($conf['server_id'], 'mail');
MC 312             if ( substr($mail_config['dkim_path'], strlen($mail_config['dkim_path'])-1) == '/' )
313                 $mail_config['dkim_path'] = substr($mail_config['dkim_path'], 0, strlen($mail_config['dkim_path'])-1);
314             if ($this->write_dkim_key($mail_config['dkim_path']."/".$data['new']['domain'], $data['new']['dkim_private'], $data['new']['domain'])) {
76be2b 315                 if ($this->add_to_amavis($data['new']['domain'], $data['new']['dkim_selector'], $data['old']['dkim_selector'] )) {
7805e1 316                     $this->restart_amavis();
FS 317                 } else {
318                     $this->remove_dkim_key($mail_config['dkim_path']."/".$data['new']['domain'], $data['new']['domain']);
319                 }
b9ea02 320             } else {
8d3466 321                 $app->log('Error saving the DKIM Private-key for '.$data['new']['domain'].' - DKIM is not enabled for the domain.', LOGLEVEL_DEBUG);
b9ea02 322             }
ec5716 323         }
T 324     }
325
b9ea02 326     /**
b1a6a5 327      * This function controlls the removement of keyfiles (public and private)
MC 328      * and the entry in the amavisd-config
329      * @param array $data mail-settings
330      */
331     function remove_dkim($_data) {
332         global $app;
333         $mail_config = $app->getconf->get_server_config($conf['server_id'], 'mail');
334         if ( substr($mail_config['dkim_path'], strlen($mail_config['dkim_path'])-1) == '/' )
335             $mail_config['dkim_path'] = substr($mail_config['dkim_path'], 0, strlen($mail_config['dkim_path'])-1);
336         $this->remove_dkim_key($mail_config['dkim_path']."/".$_data['domain'], $_data['domain']);
7805e1 337         if ($this->remove_from_amavis($_data['domain']))
FS 338             $this->restart_amavis();
b1a6a5 339     }
MC 340
341     /**
342      * Function called by onLoad
343      * deletes dkim-keys
344      */
345     function domain_dkim_delete($event_name, $data) {
7805e1 346         if (isset($data['old']['dkim']) && $data['old']['dkim'] == 'y' && $data['old']['active'] == 'y')
FS 347             $this->remove_dkim($data['old']);
56f698 348     }
b1a6a5 349
b9ea02 350     /**
b1a6a5 351      * Function called by onLoad
MC 352      * insert dkim-keys
353      */
354     function domain_dkim_insert($event_name, $data) {
7805e1 355         if (isset($data['new']['dkim']) && $data['new']['dkim']=='y' && $this->check_system($data))
ec5716 356             $this->add_dkim($data);
T 357     }
56f698 358
b9ea02 359     /**
b1a6a5 360      * Function called by onLoad
MC 361      * chang dkim-settings
362      */
363     function domain_dkim_update($event_name, $data) {
56f698 364         global $app;
8d3466 365         if($data['new']['dkim'] == 'y' || $data['old']['dkim'] == 'y'){
MC 366             if ($this->check_system($data)) {
367                 /* maildomain disabled */
368                 if ($data['new']['active'] == 'n' && $data['old']['active'] == 'y' && $data['new']['dkim']=='y') {
369                     $app->log('Maildomain '.$data['new']['domain'].' disabled - remove DKIM-settings', LOGLEVEL_DEBUG);
c6d29c 370                     $this->remove_dkim($data['new']);
FS 371                 }
8d3466 372                 /* maildomain re-enabled */
MC 373                 if ($data['new']['active'] == 'y' && $data['old']['active'] == 'n' && $data['new']['dkim']=='y') 
c6d29c 374                     $this->add_dkim($data);
FS 375
8d3466 376                 /* maildomain active - only dkim changes */
MC 377                 if ($data['new']['active'] == 'y' && $data['old']['active'] == 'y') {
378                     /* dkim disabled */
379                     if ($data['new']['dkim'] != $data['old']['dkim'] && $data['new']['dkim'] == 'n') {
380                         $this->remove_dkim($data['new']);
381                     }
382                     /* dkim enabled */
383                     elseif ($data['new']['dkim'] != $data['old']['dkim'] && $data['new']['dkim'] == 'y') {
384                         $this->add_dkim($data);
385                     }
386                     /* new private-key */
387                     if ($data['new']['dkim_private'] != $data['old']['dkim_private'] && $data['new']['dkim'] == 'y') {
388                         $this->add_dkim($data);
389                     }
390                     /* new selector */
391                     if ($data['new']['dkim_selector'] != $data['old']['dkim_selector'] && $data['new']['dkim'] == 'y') {
392                         $this->add_dkim($data);
393                     }
394                     /* new domain-name */
395                     if ($data['new']['domain'] != $data['old']['domain']) {
396                         $this->remove_dkim($data['old']);
397                         $this->add_dkim($data);
398                     }
399                 }
400
401                 /* resync */
402                 if ($data['new']['active'] == 'y' && $data['new'] == $data['old'] && $data['new']['dkim']=='y') {
403                     $this->add_dkim($data);
404                 }
b1a6a5 405             }
56f698 406         }
T 407     }
b1a6a5 408
56f698 409 }
b1a6a5 410
56f698 411 ?>