bind_service.inc 8.11 KB
Newer Older
1 2 3 4 5 6 7 8
<?php

/**
 * Implementation of the DNS service through BIND9
 *
 * A lot of this is inspired by the Apache implementation of the HTTP service.
 */
class provisionService_dns_bind extends provisionService_dns {
9 10 11 12 13 14 15 16 17
  public $has_restart_command = TRUE;

  
  static function bind_default_restart_cmd() {
    return "/etc/init.d/named restart";
  }

  function default_restart_cmd() {
    return provisionService_dns_bind::bind_default_restart_cmd();
18 19 20 21
  }

  function init() {
    parent::init();
22
    $this->server->setProperty('bind_conf_path', $this->server->config_path . '/bind.conf');
23
    $this->server->setProperty('bind_zone_master_path', $this->server->config_path . '/zones/master');
24 25
  }

26 27 28 29
  function config_data($config, $class) {
    $data = parent::config_data($config, $class);
    $data['bind_conf_path'] = $this->server->bind_config_path;
    $data['bind_zone_master_path'] = $this->server->bind_zone_master_path;
30 31
  }

32
  function verify() {
33 34 35
    if ($this->context->type =='server') {
      provision_file()->create_dir($this->server->bind_zone_master_path, dt("Bind zone files"), 0700);
    }
36 37
  }

38 39 40
  /**
   * Restart the server to pick up the new configuration files.
   */
anarcat's avatar
anarcat committed
41
  function commit() {
42 43
    // the restart_cmd stuff is part of the base server class now.
    $this->restart();
44
  }
45

46 47
  function create_zone($zonename) {
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
48
    if (!$zone->exists()) {
49
      drush_log("creating zone");
50
      //$zone->add_line_if_not_exists($zone->zone_declaration(), '/zone\s*"'. $zonename . '"/');
51
      $zone->write();
52

53 54
    } else {
      drush_log("zone already exists");
55
    }
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
    $this->load_from_zone($zone);
    parent::create_zone($zonename);
  }

  function load_from_zone($zone) {
    // load parameters from zone
    foreach (array('ttl', 'serial', 'refresh', 'retry', 'expire', 'negativettl') as $param) {
      if (isset($zone->data[$param])) {
        drush_log("got param $param from zone, value: " . $zone->data[$param]);
        if ($param == 'serial') {
          $this->$param = provisionService_dns::increment_serial($zone->data['serial']);
        } else {
          $this->$param = $zone->data[$param];
        }
      } else {
        if ($param == 'serial') {
          $this->$param = provisionService_dns::increment_serial();
        } else {
          $longparam = "dns_" . $param;
          $this->$param = $this->server->$longparam;
        }
        drush_log("param $param missing from zone, using default: " . $this->$param);
      }
    }        
80 81 82 83 84 85 86
  }

  function delete_zone($zonename) {
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
    if (!$zone->count_records(null, array('NS', 'SOA'))) {
      $zone->delete_file();
    }
87 88
  }

89
  function add_record($zonename, $name, $type, $destination) {
90
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
anarcat's avatar
anarcat committed
91
    if ($type == 'SOA') { // only one SOA per file
92
      return $zone->add_line_if_not_exists($name . ' IN SOA ' . $destination, '/(?:@\s+)?IN\s+SOA\s+/');
anarcat's avatar
anarcat committed
93 94
    } else {
      return $zone->add_line($name . "\tIN\t" . $type . "\t" . $destination);
95 96 97
    }
  }

98
  function edit_record($zonename, $name, $type, $destination) {
99
    drush_log("edit: $zonename, $name, $type, $destination");
100
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
101 102 103
    $pattern = "/^\s*$name\s+IN\s+$type\s+.*$/im";
    if ($type == 'SOA') {
      $pattern = "/^(?:@\s+)?IN\s+SOA\s+[\w.]+\s+[\w.]+\s+\(([^)]*)\)\s*$/ims";
anarcat's avatar
anarcat committed
104
    }
105
    return $zone->replace_or_add_line($name . "\tIN\t" . $type . "\t" . $destination, $pattern);
106 107
  }

108
  function delete_record($zonename, $name, $type = null, $destination = null) {
anarcat's avatar
anarcat committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
    $pattern = "/^$name\s\s*IN\s\s*";
    if (is_null($type)) {
      $pattern .= "\w\w*";
    } else {
      $pattern .= $type;
    }
    $pattern .= "\s\s*";
    if (!is_null($destination)) {
      $pattern .= $destination;
      $pattern .= '$';
    }
    $pattern .= '/';
    return $zone->delete_line($pattern);
  }

anarcat's avatar
anarcat committed
125
  function zone_exists($zonename) {
126 127 128
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
    return $zone->exists();
  }
129

anarcat's avatar
anarcat committed
130
  function count_records($zonename, $include = null, $exclude = null) {
131 132 133 134 135
    $zone = new provisionConfig_bind_zone($this->context, array('name' => $zonename));
    return $zone->count_records($include, $exclude);
  }

}
136

anarcat's avatar
anarcat committed
137 138 139
class provisionConfig_bind_zone extends provisionConfig {
  public $template = 'zone.tpl.php';

anarcat's avatar
anarcat committed
140
  function parse() {
141 142
    $file = $this->filename();
    $body = file_get_contents($file);
anarcat's avatar
anarcat committed
143
    $data = array();
144
    if (preg_match('/^(?:@\s+)?IN\s+SOA\s+[\w.]+\s+[\w.]+\s+\(([^)]*)\)\s*$/ims', $body, $matches)) {
145 146
      $soa = trim($matches[1]);
      $SOA = preg_split('/\s\s*/', $soa);
147 148 149 150
      $i = 0;
      foreach (array('serial', 'refresh', 'retry', 'expire', 'negativettl') as $param) {
        $data[$param] = $SOA[$i++];
      }
151 152 153
      drush_log("parsed SOA from zonefile:");
      foreach ($data as $key => $value) {
        drush_log($key . ": " . $value);
anarcat's avatar
anarcat committed
154
      }
155 156
    } else {
      drush_log("no SOA found in zonefile $file, body: ($body)");
157
    }
anarcat's avatar
anarcat committed
158 159 160
    return $data;
  }

anarcat's avatar
anarcat committed
161
  function filename() {
162
    return $this->bind_zone_master_path . '/' . $this->data['name'];
anarcat's avatar
anarcat committed
163 164 165
  }

  function exists() {
166 167
    drush_log("checking file: " . $this->filename());
    return provision_file()->exists($this->filename())->status();
168
  }
anarcat's avatar
anarcat committed
169

170 171 172 173 174 175 176
  /**
   * This returns the number of records in the zone.
   */
  function count_records($include = null, $exclude = null) {
    return TRUE;
  }

anarcat's avatar
anarcat committed
177 178 179 180 181 182 183 184 185
  /**
   * add a line to a file if the file (or pattern) is not in the file
   * already
   *
   * This is bound to be called repeatedly so it needs to be fast.
   */
  function add_line_if_not_exists($line, $pattern = null) {
    $file = $this->filename();
    // XXX: we should do exponential backoff here to limit contention
186 187
    $fd = fopen($file, 'r+'); // read/write, beginning of file
    flock($fd, LOCK_EX);
anarcat's avatar
anarcat committed
188 189
    $line = trim($line);
    if (is_null($pattern)) {
190
      $pattern = '/' . $line . '/';
anarcat's avatar
anarcat committed
191 192 193 194 195 196 197
    }
    $found = FALSE;
    while ($l = fgets($fd)) {
      if (preg_match($pattern, $l)) {
        $found = TRUE;
        break;
      }
198
    }
anarcat's avatar
anarcat committed
199
    if (!$found) {
200
      fseek($fd, 0, SEEK_END);
201
      drush_log("pattern $pattern not found in file $file, adding at the end: $line");
anarcat's avatar
anarcat committed
202 203 204 205
      fwrite($fd, $line . "\n");
    }
    fclose($fd);
    return $found;
206
  }
anarcat's avatar
anarcat committed
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  /**
   * add a line to a file if the file (or pattern) is not in the file
   * already
   *
   * This is bound to be called repeatedly so it needs to be fast.
   */
  function add_line($line) {
    $file = $this->filename();
    // XXX: we should do exponential backoff here to limit contention
    $fd = fopen($file, 'a'); // read/write, beginning of file
    flock($fd, LOCK_EX);
    $line = trim($line);
    fwrite($fd, $line . "\n");
    fclose($fd);
  }

  function replace_or_add_line($replacement, $pattern) {
    $file = $this->filename();
226 227 228 229
    if (!($fd = fopen($file, 'a+'))) {
      drush_log("warning: cannot open $file");
    }

230 231 232 233
    flock($fd, LOCK_EX);
    $body = fread($fd, filesize($file));
    $newbody = preg_replace($pattern, $replacement, $body);
    if ($body === $newbody) {
234
      drush_log("pattern $pattern not found in $file, adding at the end: $replacement");
235 236 237 238 239 240 241 242 243 244 245
      fseek($fd, 0, SEEK_END);
      fwrite($fd, $replacement . "\n");
    } else {
      drush_log("pattern $pattern found and replaced with $replacement");
      fseek($fd, 0);
      fwrite($fd, $newbody);
      ftruncate($fd, strlen($newbody));
    }
    fclose($fd);    
  }

anarcat's avatar
anarcat committed
246 247 248 249 250 251 252 253 254 255 256 257 258
  function delete_line($pattern) {
    $file = $this->filename();
    $fd = fopen($file, 'r+');
    flock($fd, LOCK_EX);
    $body = fread($fd, filesize($file));
    $newbody = preg_replace($pattern, '', $body);
    drush_log("pattern $pattern found and replaced with $replacement");
    fseek($fd, 0);
    fwrite($fd, $newbody);
    ftruncate($fd, strlen($newbody));
    fclose($fd);    
  }

anarcat's avatar
anarcat committed
259 260
  function zone_declaration() {
    return 'zone "' . $this->data['name'] . '" { type master; file "' . $this->filename() . '"; allow-query { any; }; };';
261 262
  }

anarcat's avatar
anarcat committed
263 264
  
}