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