Alternc  3.2
Alternc logiel libre pour l'h├ębergement
 All Data Structures Namespaces Files Functions Variables Pages
class_system_bind.php
Go to the documentation of this file.
1 <?php
2 
3 
4 class system_bind {
5  var $ZONE_TEMPLATE ="/etc/alternc/templates/bind/templates/zone.template";
6  var $NAMED_TEMPLATE ="/etc/alternc/templates/bind/templates/named.template";
7  var $NAMED_CONF ="/var/lib/alternc/bind/automatic.conf";
8  var $RNDC ="/usr/sbin/rndc";
9 
10  var $dkim_trusted_host_file = "/etc/opendkim/TrustedHosts";
11  var $dkim_keytable_file = "/etc/opendkim/KeyTable";
12  var $dkim_signingtable_file = "/etc/opendkim/SigningTable";
13 
14  var $cache_conf_db = array();
15  var $cache_get_persistent = array();
16  var $cache_zone_file = array();
17  var $cache_domain_summary = array();
18  var $zone_file_directory = '/var/lib/alternc/bind/zones/';
19 
20  function system_bind() {
21  // Constructeur
22  }
23 
24  // Return the part of the conf we got from the database
25  function conf_from_db($domain=false) {
26  global $db;
27  // Use cache, fill cache if empty
28  if (empty($this->cache_conf_db)) {
29  $db->query("
30  select
31  sd.domaine,
32  replace(replace(dt.entry,'%TARGET%',sd.valeur), '%SUB%', if(length(sd.sub)>0,sd.sub,'@')) as entry
33  from
34  sub_domaines sd,
35  domaines_type dt
36  where
37  sd.type=dt.name
38  and sd.enable in ('ENABLE', 'ENABLED')
39  order by entry ;");
40  while ($db->next_record()) {
41  $t[$db->f('domaine')][] = $db->f('entry');
42  }
43  $this->cache_conf_db = $t;
44  }
45  if ($domain) {
46  if (isset($this->cache_conf_db[$domain])) {
47  return $this->cache_conf_db[$domain];
48  } else {
49  return array();
50  }
51  } // if domain
52  return $this->cache_conf_db;
53  }
54 
55  // Return full path of the zone configuration file
57  return $this->zone_file_directory.$domain;
58  }
59 
60  function get_zone_file($domain) {
61  // Use cache, fill cache if empty
62  if (!isset($this->cache_zone_file[$domain]) ) {
63  if (file_exists($this->get_zone_file_uri($domain))) {
64  $this->cache_zone_file[$domain] = @file_get_contents($this->get_zone_file_uri($domain));
65  } else {
66  $this->cache_zone_file[$domain] = false;
67  }
68  }
69  return $this->cache_zone_file[$domain] ;
70  }
71 
72  function get_serial($domain) {
73  // Return the next serial the domain must have.
74  // Choose between a generated and an incremented.
75 
76  // Calculated :
77  $calc = date('Ymd').'00'."\n";
78 
79  // Old one :
80  $old=$calc; // default value
81  $file = $this->get_zone_file($domain);
82  preg_match_all("/\s*(\d{10})\s+\;\sserial\s?/", $file, $output_array);
83  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
84  $old = $output_array[1][0];
85  }
86 
87  // Return max between newly calculated, and old one incremented
88  return max(array($calc,$old)) + 1 ;
89  }
90 
91  // Return lines that are after ;;;END ALTERNC AUTOGENERATE CONFIGURATION
92  function get_persistent($domain) {
93  if ( ! isset($this->cache_get_persistent[$domain] )) {
94  preg_match_all('/\;\s*END\sALTERNC\sAUTOGENERATE\sCONFIGURATION(.*)/s', $this->get_zone_file($domain), $output_array);
95  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
96  $this->cache_get_persistent[$domain] = $output_array[1][0];
97  } else {
98  $this->cache_get_persistent[$domain] = false;
99  }
100  } // isset
101  return $this->cache_get_persistent[$domain];
102  }
103 
105  return file_get_contents($this->ZONE_TEMPLATE);
106  }
107 
108  function get_domain_summary($domain=false) {
109  global $dom;
110 
111  // Use cache if is filled, if not, fill it
112  if (empty($this->cache_domain_summary)) {
113  $this->cache_domain_summary = $dom->get_domain_all_summary();
114  }
115 
116  if ($domain) return $this->cache_domain_summary[$domain];
117  else return $this->cache_domain_summary;
118  }
119 
120  function dkim_delete($domain) {
121  $target_dir = "/etc/opendkim/keys/$domain";
122  if (file_exists($target_dir)) {
123  @unlink("$target_dir/alternc_private");
124  @unlink("$target_dir/alternc.txt");
125  @rmdir($target_dir);
126  }
127  return true;
128  }
129 
130  // Generate the domain DKIM key
132  // Stop here if we do not manage the mail
133  if ( ! $this->get_domain_summary($domain)['gesmx'] ) return;
134 
135  $target_dir = "/etc/opendkim/keys/$domain";
136 
137  if (file_exists($target_dir.'/alternc.txt')) return; // Do not generate if exist
138 
139  if (! is_dir($target_dir)) mkdir($target_dir); // create dir
140 
141  // Generate the key
142  $old_dir=getcwd();
143  chdir($target_dir);
144  exec('opendkim-genkey -r -d "'.escapeshellarg($domain).'" -s "alternc" ');
145  chdir($old_dir);
146 
147  // opendkim must be owner of the key
148  chown("$target_dir/alternc.private", 'opendkim');
149  chgrp("$target_dir/alternc.private", 'opendkim');
150 
151  return true; // FIXME handle error
152  }
153 
154  // Refresh DKIM configuration: be sure to list the domain having a private key (and only them)
155  function dkim_refresh_list() { // so ugly... but there is only 1 pass, not 3. Still ugly.
156  $trusted_host_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n";
157  $keytable_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n";
158  $signingtable_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n";
159 
160  # Generate automatic entry
161  foreach ($this->get_domain_summary() as $domain => $ds ) {
162  // Skip if delete in progress, or if we do not manage dns or mail
163  if ( ! $ds['gesdns'] || ! $ds['gesmx'] || strtoupper($ds['dns_action']) == 'DELETE' ) continue;
164 
165  // Skip if there is no key generated
166  if (! file_exists("/etc/opendkim/keys/$domain/alternc.txt")) continue;
167 
168  // Modif the files.
169  $trusted_host_new.="$domain\n";
170  $keytable_new .="alternc._domainkey.$domain $domain:alternc:/etc/opendkim/keys/$domain/alternc.private\n";
171  $signingtable_new.="$domain alternc._domainkey.$domain\n";
172  }
173  $trusted_host_new.="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE";
174  $keytable_new .="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE";
175  $signingtable_new.="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE";
176 
177  # Get old files
178  $trusted_host_old=@file_get_contents($this->dkim_trusted_host_file);
179  $keytable_old =@file_get_contents($this->dkim_keytable_file);
180  $signingtable_old=@file_get_contents($this->dkim_signingtable_file);
181 
182  # Keep manuel entry
183  preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $trusted_host_old, $output_array);
184  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
185  $trusted_host_new.=$output_array[1][0];
186  }
187  preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $keytable_old, $output_array);
188  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
189  $keytable_new.=$output_array[1][0];
190  }
191  preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $signingtable_old, $output_array);
192  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
193  $signingtable_new.=$output_array[1][0];
194  }
195 
196  // Save if there are some diff
197  if ( $trusted_host_new != $trusted_host_old ) {
198  file_put_contents($this->dkim_trusted_host_file, $trusted_host_new);
199  }
200  if ( $keytable_new != $keytable_old ) {
201  file_put_contents($this->dkim_keytable_file, $keytable_new);
202  }
203  if ( $signingtable_new != $signingtable_old ) {
204  file_put_contents($this->dkim_signingtable_file, $signingtable_new);
205  }
206 
207  }
208 
209  function dkim_entry($domain) {
210  $keyfile="/etc/opendkim/keys/$domain/alternc.txt";
211  if (! file_exists($keyfile) && $this->get_domain_summary($domain)['gesmx'] ) {
212  $this->dkim_generate_key($domain);
213  }
214  return @file_get_contents($keyfile);
215  }
216 
217  // Conditionnal generation autoconfig entry for outlook / thunderbird
218  // If entry with the same name allready exist, skip it.
220  $zone= implode("\n",$this->conf_from_db($domain))."\n".$this->get_persistent($domain);
221 
222  $entry='';
223  if ( $this->get_domain_summary($domain)['gesmx'] ) {
224  // If we manage the mail
225 
226  // Check if there is no the same entry (defined or manual)
227  // can be toto IN A or toto.fqdn.tld. IN A
228  if (! preg_match("/autoconfig(\s|\.".str_replace('.','\.',$domain)."\.)/", $zone )) {
229  $entry.="autoconfig IN CNAME %%fqdn%%.\n";
230  }
231  if (! preg_match("/autodiscover(\s|\.".str_replace('.','\.',$domain)."\.)/", $zone )) {
232  $entry.="autodiscover IN CNAME %%fqdn%%.\n";
233  }
234  } // if gesmx
235  return $entry;
236  }
237 
238  // Return a fully generated zone
239  function get_zone($domain) {
240  global $L_FQDN, $L_NS1_HOSTNAME, $L_NS2_HOSTNAME, $L_DEFAULT_MX, $L_DEFAULT_SECONDARY_MX, $L_PUBLIC_IP;
241 
242  $zone =$this->get_zone_header($domain);
243  $zone.=implode("\n",$this->conf_from_db($domain));
244  $zone.="\n;;;HOOKED ENTRY\n";
245 
246  $zone.= $this->dkim_entry($domain);
247  $zone.= $this->mail_autoconfig_entry($domain);
248 
249  $zone.="\n;;;END ALTERNC AUTOGENERATE CONFIGURATION";
250  $zone.=$this->get_persistent($domain);
251 
252  // FIXME check those vars
253  $zone = strtr($zone, array(
254  "%%fqdn%%"=>"$L_FQDN",
255  "%%ns1%%"=>"$L_NS1_HOSTNAME",
256  "%%ns2%%"=>"$L_NS2_HOSTNAME",
257  "%%DEFAULT_MX%%"=>"$L_DEFAULT_MX",
258  "%%DEFAULT_SECONDARY_MX%%"=>"$L_DEFAULT_SECONDARY_MX",
259  "@@fqdn@@"=>"$L_FQDN",
260  "@@ns1@@"=>"$L_NS1_HOSTNAME",
261  "@@ns2@@"=>"$L_NS2_HOSTNAME",
262  "@@DEFAULT_MX@@"=>"$L_DEFAULT_MX",
263  "@@DEFAULT_SECONDARY_MX@@"=>"$L_DEFAULT_SECONDARY_MX",
264  "@@DOMAINE@@"=>"$domain",
265  "@@SERIAL@@"=>$this->get_serial($domain),
266  "@@PUBLIC_IP@@"=>"$L_PUBLIC_IP",
267  "@@ZONETTL@@"=> $this->get_domain_summary($domain)['zonettl'],
268  ));
269 
270  return $zone;
271  }
272 
273  function reload_zone($domain) {
274  exec($this->RNDC." reload ".escapeshellarg($domain), $output, $return_value);
275  if ($return_value != 0 ) {
276  echo "ERROR: Reload zone failed for zone $domain\n";
277  }
278  }
279 
280  // return true if zone is locked
281  function is_locked($domain) {
282  preg_match_all("/(\;\s*LOCKED:YES)/i", $this->get_zone_file($domain), $output_array);
283  if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
284  return true;
285  }
286  return false;
287  }
288 
289  function save_zone($domain) {
290  global $db, $dom;
291 
292  // Do not save if the zone is LOCKED
293  if ( $this->is_locked($domain)) {
294  $dom->set_dns_result($domain, "The zone file of this domain is locked. Contact your administrator."); // If edit, change dummy_for_translation
295  $dom->set_dns_action($domain, 'OK');
296  return false;
297  }
298 
299  // Save file, and apply chmod/chown
300  $file=$this->get_zone_file_uri($domain);
301  file_put_contents($file, $this->get_zone($domain));
302  chown($file, 'bind');
303  chmod($file, 0640);
304 
305  $dom->set_dns_action($domain, 'OK');
306  return true; // fixme add tests
307  }
308 
309  // Delete the zone configuration file
310  function delete_zone($domain) {
311  $file=$this->get_zone_file_uri($domain);
312  if (file_exists($file)) {
313  unlink($file);
314  }
315  $this->dkim_delete($domain);
316  return;
317  }
318 
319  function reload_named() {
320  global $hooks;
321  // Generate the new conf file
322  $new_named_conf="// DO NOT EDIT\n// This file is generated by Alternc.\n// Every changes you'll make will be overwrited.\n";
323  $tpl=file_get_contents($this->NAMED_TEMPLATE);
324  foreach ($this->get_domain_summary() as $domain => $ds ) {
325  if ( ! $ds['gesdns'] || strtoupper($ds['dns_action']) == 'DELETE' ) continue;
326  $new_named_conf.=strtr($tpl, array("@@DOMAINE@@"=>$domain, "@@ZONE_FILE@@"=>$this->get_zone_file_uri($domain)));
327  }
328 
329  // Get the actual conf file
330  $old_named_conf = @file_get_contents($this->NAMED_CONF);
331 
332  // Apply new configuration only if there are some differences
333  if ($old_named_conf != $new_named_conf ) {
334  file_put_contents($this->NAMED_CONF,$new_named_conf);
335  chown($this->NAMED_CONF, 'bind');
336  chmod($this->NAMED_CONF, 0640);
337  exec($this->RNDC." reconfig");
338  $hooks->invoke_scripts("/usr/lib/alternc/reload.d", array('dns_reconfig') );
339  }
340 
341  return true;
342  }
343 
344  // Regenerate bind configuration and load it
345  function regenerate_conf($all=false) {
346  global $hooks;
347 
348  foreach ($this->get_domain_summary() as $domain => $ds ) {
349  if ( ! $ds['gesdns'] && strtoupper($ds['dns_action']) == 'OK' ) continue; // Skip if we do not manage DNS and is up-to-date for this domain
350 
351  if ( (strtoupper($ds['dns_action']) == 'DELETE' ) ||
352  (strtoupper($ds['dns_action']) == 'UPDATE' && $ds['gesdns']==false ) // in case we update the zone to disable DNS management
353  ) {
354  $this->delete_zone($domain);
355  continue;
356  }
357 
358  if ( ( $all || strtoupper($ds['dns_action']) == 'UPDATE' ) && $ds['gesdns'] ) {
359  $this->save_zone($domain);
360  $this->reload_zone($domain);
361  $hooks->invoke_scripts("/usr/lib/alternc/reload.d", array('dns_reload_zone', $domain) );
362  }
363  } // end foreach domain
364 
365  $this->dkim_refresh_list();
366  $this->reload_named();
367 
368  return;
369  }
370 
371  private function dummy_for_translation() {
372  _("The zone file of this domain is locked. Contact your administrator.");
373  }
374 
375 } // class
376 
377 
378 ?>