commit | author | age
|
7dbea0
|
1 |
<?php |
T |
2 |
|
|
3 |
/* |
|
4 |
Copyright (c) 2009, Till Brehm, projektfarm Gmbh |
|
5 |
All rights reserved. |
|
6 |
|
|
7 |
Redistribution and use in source and binary forms, with or without modification, |
|
8 |
are permitted provided that the following conditions are met: |
|
9 |
|
|
10 |
* Redistributions of source code must retain the above copyright notice, |
|
11 |
this list of conditions and the following disclaimer. |
|
12 |
* Redistributions in binary form must reproduce the above copyright notice, |
|
13 |
this list of conditions and the following disclaimer in the documentation |
|
14 |
and/or other materials provided with the distribution. |
|
15 |
* Neither the name of ISPConfig nor the names of its contributors |
|
16 |
may be used to endorse or promote products derived from this software without |
|
17 |
specific prior written permission. |
|
18 |
|
|
19 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
20 |
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
21 |
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
22 |
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
|
23 |
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
24 |
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
|
26 |
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
27 |
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
|
28 |
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 |
*/ |
|
30 |
|
|
31 |
class bind_plugin { |
7fe908
|
32 |
|
7dbea0
|
33 |
var $plugin_name = 'bind_plugin'; |
T |
34 |
var $class_name = 'bind_plugin'; |
|
35 |
var $action = 'update'; |
7fe908
|
36 |
|
7dbea0
|
37 |
//* This function is called during ispconfig installation to determine |
T |
38 |
// if a symlink shall be created for this plugin. |
|
39 |
function onInstall() { |
|
40 |
global $conf; |
7fe908
|
41 |
|
4bd960
|
42 |
if(isset($conf['bind']['installed']) && $conf['bind']['installed'] == true && @is_link('/usr/local/ispconfig/server/mods-enabled/dns_module.inc.php')) { |
7dbea0
|
43 |
return true; |
T |
44 |
} else { |
|
45 |
return false; |
|
46 |
} |
7fe908
|
47 |
|
7dbea0
|
48 |
} |
7fe908
|
49 |
|
MC |
50 |
|
7dbea0
|
51 |
/* |
T |
52 |
This function is called when the plugin is loaded |
|
53 |
*/ |
7fe908
|
54 |
|
7dbea0
|
55 |
function onLoad() { |
T |
56 |
global $app; |
7fe908
|
57 |
|
7dbea0
|
58 |
/* |
T |
59 |
Register for the events |
|
60 |
*/ |
a59731
|
61 |
|
7fe908
|
62 |
//* SOA |
MC |
63 |
$app->plugins->registerEvent('dns_soa_insert', $this->plugin_name, 'soa_insert'); |
|
64 |
$app->plugins->registerEvent('dns_soa_update', $this->plugin_name, 'soa_update'); |
|
65 |
$app->plugins->registerEvent('dns_soa_delete', $this->plugin_name, 'soa_delete'); |
|
66 |
|
|
67 |
//* SLAVE |
|
68 |
$app->plugins->registerEvent('dns_slave_insert', $this->plugin_name, 'slave_insert'); |
|
69 |
$app->plugins->registerEvent('dns_slave_update', $this->plugin_name, 'slave_update'); |
|
70 |
$app->plugins->registerEvent('dns_slave_delete', $this->plugin_name, 'slave_delete'); |
|
71 |
|
7dbea0
|
72 |
//* RR |
7fe908
|
73 |
$app->plugins->registerEvent('dns_rr_insert', $this->plugin_name, 'rr_insert'); |
MC |
74 |
$app->plugins->registerEvent('dns_rr_update', $this->plugin_name, 'rr_update'); |
|
75 |
$app->plugins->registerEvent('dns_rr_delete', $this->plugin_name, 'rr_delete'); |
|
76 |
|
7dbea0
|
77 |
} |
7fe908
|
78 |
|
MC |
79 |
|
|
80 |
function soa_insert($event_name, $data) { |
7dbea0
|
81 |
global $app, $conf; |
7fe908
|
82 |
|
7dbea0
|
83 |
$this->action = 'insert'; |
7fe908
|
84 |
$this->soa_update($event_name, $data); |
MC |
85 |
|
7dbea0
|
86 |
} |
7fe908
|
87 |
|
MC |
88 |
function soa_update($event_name, $data) { |
7dbea0
|
89 |
global $app, $conf; |
7fe908
|
90 |
|
fc70a2
|
91 |
//* Load libraries |
T |
92 |
$app->uses("getconf,tpl"); |
7fe908
|
93 |
|
7dbea0
|
94 |
//* load the server configuration options |
T |
95 |
$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns'); |
7fe908
|
96 |
|
7dbea0
|
97 |
//* Write the domain file |
fdb514
|
98 |
if(!empty($data['new']['id'])) { |
8ee180
|
99 |
$tpl = new tpl(); |
T |
100 |
$tpl->newTemplate("bind_pri.domain.master"); |
7fe908
|
101 |
|
8ee180
|
102 |
$zone = $data['new']; |
T |
103 |
$tpl->setVar($zone); |
7fe908
|
104 |
|
8ee180
|
105 |
$records = $app->db->queryAllRecords("SELECT * FROM dns_rr WHERE zone = ".$zone['id']." AND active = 'Y'"); |
615a0a
|
106 |
if(is_array($records) && !empty($records)){ |
T |
107 |
for($i=0;$i<sizeof($records);$i++){ |
|
108 |
if($records[$i]['ttl'] == 0) $records[$i]['ttl'] = ''; |
|
109 |
} |
|
110 |
} |
7fe908
|
111 |
$tpl->setLoop('zones', $records); |
MC |
112 |
|
4b88c2
|
113 |
//TODO : change this when distribution information has been integrated into server record |
7fe908
|
114 |
if (file_exists('/etc/gentoo-release')) { |
MC |
115 |
$filename = escapeshellcmd($dns_config['bind_zonefiles_dir'].'/pri.'.str_replace("/", "_", substr($zone['origin'], 0, -1))); |
|
116 |
} |
|
117 |
else { |
|
118 |
$filename = escapeshellcmd($dns_config['bind_zonefiles_dir'].'/pri.'.str_replace("/", "_", substr($zone['origin'], 0, -1))); |
|
119 |
} |
|
120 |
|
|
121 |
file_put_contents($filename, $tpl->grab()); |
8e725d
|
122 |
chown($filename, escapeshellcmd($dns_config['bind_user'])); |
J |
123 |
chgrp($filename, escapeshellcmd($dns_config['bind_group'])); |
7fe908
|
124 |
|
f038c0
|
125 |
//* Check the zonefile |
T |
126 |
if(is_file($filename.'.err')) unlink($filename.'.err'); |
7fe908
|
127 |
exec('named-checkzone '.escapeshellarg($zone['origin']).' '.escapeshellarg($filename), $out, $return_status); |
f038c0
|
128 |
if($return_status === 0) { |
7fe908
|
129 |
$app->log("Writing BIND domain file: ".$filename, LOGLEVEL_DEBUG); |
f038c0
|
130 |
} else { |
7fe908
|
131 |
$app->log("Writing BIND domain file failed: ".$filename." ".implode(' ', $out), LOGLEVEL_WARN); |
MC |
132 |
rename($filename, $filename.'.err'); |
f038c0
|
133 |
} |
8ee180
|
134 |
unset($tpl); |
T |
135 |
unset($records); |
a59ad3
|
136 |
unset($records_out); |
8ee180
|
137 |
unset($zone); |
T |
138 |
} |
7fe908
|
139 |
|
7dbea0
|
140 |
//* rebuild the named.conf file if the origin has changed or when the origin is inserted. |
fc70a2
|
141 |
//if($this->action == 'insert' || $data['old']['origin'] != $data['new']['origin']) { |
7fe908
|
142 |
$this->write_named_conf($data, $dns_config); |
fc70a2
|
143 |
//} |
7fe908
|
144 |
|
7dbea0
|
145 |
//* Delete old domain file, if domain name has been changed |
T |
146 |
if($data['old']['origin'] != $data['new']['origin']) { |
4b88c2
|
147 |
//TODO : change this when distribution information has been integrated into server record |
7fe908
|
148 |
if (file_exists('/etc/gentoo-release')) { |
MC |
149 |
$filename = $dns_config['bind_zonefiles_dir'].'/pri.'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
150 |
} |
|
151 |
else { |
|
152 |
$filename = $dns_config['bind_zonefiles_dir'].'/pri.'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
153 |
} |
|
154 |
|
f038c0
|
155 |
if(is_file($filename)) unlink($filename); |
T |
156 |
if(is_file($filename.'.err')) unlink($filename.'.err'); |
7dbea0
|
157 |
} |
7fe908
|
158 |
|
eb64c3
|
159 |
//* Restart bind nameserver if update_acl is not empty, otherwise reload it |
TB |
160 |
if($data['new']['update_acl'] != '') { |
|
161 |
$app->services->restartServiceDelayed('bind', 'restart'); |
|
162 |
} else { |
|
163 |
$app->services->restartServiceDelayed('bind', 'reload'); |
|
164 |
} |
7fe908
|
165 |
|
7dbea0
|
166 |
} |
a59731
|
167 |
|
7fe908
|
168 |
function soa_delete($event_name, $data) { |
a59731
|
169 |
global $app, $conf; |
7fe908
|
170 |
|
MC |
171 |
//* load the server configuration options |
|
172 |
$app->uses("getconf,tpl"); |
|
173 |
$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns'); |
|
174 |
|
|
175 |
//* rebuild the named.conf file |
|
176 |
$this->write_named_conf($data, $dns_config); |
|
177 |
|
|
178 |
//* Delete the domain file |
|
179 |
//TODO : change this when distribution information has been integrated into server record |
|
180 |
if (file_exists('/etc/gentoo-release')) { |
|
181 |
$zone_file_name = $dns_config['bind_zonefiles_dir'].'/pri/'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
182 |
} |
|
183 |
else { |
|
184 |
$zone_file_name = $dns_config['bind_zonefiles_dir'].'/pri.'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
185 |
} |
|
186 |
|
|
187 |
if(is_file($zone_file_name)) unlink($zone_file_name); |
|
188 |
if(is_file($zone_file_name.'.err')) unlink($zone_file_name.'.err'); |
|
189 |
$app->log("Deleting BIND domain file: ".$zone_file_name, LOGLEVEL_DEBUG); |
|
190 |
|
|
191 |
//* Reload bind nameserver |
|
192 |
$app->services->restartServiceDelayed('bind', 'reload'); |
|
193 |
|
a59731
|
194 |
} |
7fe908
|
195 |
|
MC |
196 |
function slave_insert($event_name, $data) { |
a59731
|
197 |
global $app, $conf; |
7fe908
|
198 |
|
MC |
199 |
$this->action = 'insert'; |
|
200 |
$this->slave_update($event_name, $data); |
|
201 |
|
|
202 |
} |
|
203 |
|
|
204 |
function slave_update($event_name, $data) { |
|
205 |
global $app, $conf; |
|
206 |
|
a59731
|
207 |
//* Load libraries |
D |
208 |
$app->uses("getconf,tpl"); |
7fe908
|
209 |
|
a59731
|
210 |
//* load the server configuration options |
D |
211 |
$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns'); |
7fe908
|
212 |
|
a59731
|
213 |
//* rebuild the named.conf file if the origin has changed or when the origin is inserted. |
D |
214 |
//if($this->action == 'insert' || $data['old']['origin'] != $data['new']['origin']) { |
7fe908
|
215 |
$this->write_named_conf($data, $dns_config); |
a59731
|
216 |
//} |
7fe908
|
217 |
|
a59731
|
218 |
//* Delete old domain file, if domain name has been changed |
D |
219 |
if($data['old']['origin'] != $data['new']['origin']) { |
4b88c2
|
220 |
//TODO : change this when distribution information has been integrated into server record |
7fe908
|
221 |
if (file_exists('/etc/gentoo-release')) { |
MC |
222 |
$filename = $dns_config['bind_zonefiles_dir'].'/sec/'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
223 |
} |
|
224 |
else { |
|
225 |
$filename = $dns_config['bind_zonefiles_dir'].'/slave/sec.'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
226 |
} |
|
227 |
|
a59731
|
228 |
if(is_file($filename)) unset($filename); |
D |
229 |
} |
7fe908
|
230 |
|
355efb
|
231 |
//* Ensure that the named slave directory is writable by the named user |
T |
232 |
if (file_exists('/etc/gentoo-release')) { |
|
233 |
$slave_record_dir = $dns_config['bind_zonefiles_dir'].'/sec'; |
|
234 |
} else { |
|
235 |
$slave_record_dir = $dns_config['bind_zonefiles_dir'].'/slave'; |
|
236 |
} |
7fe908
|
237 |
if(!@is_dir($slave_record_dir)) mkdir($slave_record_dir, 0770); |
MC |
238 |
chown($slave_record_dir, $dns_config['bind_user']); |
|
239 |
chgrp($slave_record_dir, $dns_config['bind_group']); |
|
240 |
|
a59731
|
241 |
//* Reload bind nameserver |
7fe908
|
242 |
$app->services->restartServiceDelayed('bind', 'reload'); |
MC |
243 |
|
a59731
|
244 |
} |
7fe908
|
245 |
|
MC |
246 |
function slave_delete($event_name, $data) { |
a59731
|
247 |
global $app, $conf; |
7fe908
|
248 |
|
MC |
249 |
|
a59731
|
250 |
//* load the server configuration options |
D |
251 |
$app->uses("getconf,tpl"); |
|
252 |
$dns_config = $app->getconf->get_server_config($conf["server_id"], 'dns'); |
7fe908
|
253 |
|
a59731
|
254 |
//* rebuild the named.conf file |
7fe908
|
255 |
$this->write_named_conf($data, $dns_config); |
MC |
256 |
|
a59731
|
257 |
//* Delete the domain file |
4b88c2
|
258 |
//TODO : change this when distribution information has been integrated into server record |
7fe908
|
259 |
if (file_exists('/etc/gentoo-release')) { |
MC |
260 |
$zone_file_name = $dns_config['bind_zonefiles_dir'].'/sec/'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
261 |
} |
|
262 |
else { |
|
263 |
$zone_file_name = $dns_config['bind_zonefiles_dir'].'/slave/sec.'.str_replace("/", "_", substr($data['old']['origin'], 0, -1)); |
|
264 |
} |
|
265 |
|
a59731
|
266 |
if(is_file($zone_file_name)) unlink($zone_file_name); |
7fe908
|
267 |
$app->log("Deleting BIND domain file for secondary zone: ".$zone_file_name, LOGLEVEL_DEBUG); |
MC |
268 |
|
a59731
|
269 |
//* Reload bind nameserver |
7fe908
|
270 |
$app->services->restartServiceDelayed('bind', 'reload'); |
MC |
271 |
|
7dbea0
|
272 |
|
T |
273 |
} |
7fe908
|
274 |
|
MC |
275 |
function rr_insert($event_name, $data) { |
7dbea0
|
276 |
global $app, $conf; |
7fe908
|
277 |
|
7dbea0
|
278 |
//* Get the data of the soa and call soa_update |
T |
279 |
$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ".$data['new']['zone']); |
|
280 |
$data["new"] = $tmp; |
|
281 |
$data["old"] = $tmp; |
|
282 |
$this->action = 'update'; |
7fe908
|
283 |
$this->soa_update($event_name, $data); |
MC |
284 |
|
7dbea0
|
285 |
} |
7fe908
|
286 |
|
MC |
287 |
function rr_update($event_name, $data) { |
7dbea0
|
288 |
global $app, $conf; |
7fe908
|
289 |
|
MC |
290 |
//* Get the data of the soa and call soa_update |
|
291 |
$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ".$data['new']['zone']); |
|
292 |
$data["new"] = $tmp; |
|
293 |
$data["old"] = $tmp; |
|
294 |
$this->action = 'update'; |
|
295 |
$this->soa_update($event_name, $data); |
|
296 |
|
|
297 |
} |
|
298 |
|
|
299 |
function rr_delete($event_name, $data) { |
|
300 |
global $app, $conf; |
|
301 |
|
7dbea0
|
302 |
//* Get the data of the soa and call soa_update |
531afa
|
303 |
$tmp = $app->db->queryOneRecord("SELECT * FROM dns_soa WHERE id = ".intval($data['old']['zone'])); |
7dbea0
|
304 |
$data["new"] = $tmp; |
T |
305 |
$data["old"] = $tmp; |
|
306 |
$this->action = 'update'; |
7fe908
|
307 |
$this->soa_update($event_name, $data); |
MC |
308 |
|
7dbea0
|
309 |
} |
7fe908
|
310 |
|
MC |
311 |
//################################################################## |
|
312 |
|
7dbea0
|
313 |
function write_named_conf($data, $dns_config) { |
T |
314 |
global $app, $conf; |
7fe908
|
315 |
|
MC |
316 |
//* Only write the master file for the current server |
c36fe4
|
317 |
$tmps = $app->db->queryAllRecords("SELECT origin, xfer, also_notify, update_acl FROM dns_soa WHERE active = 'Y' AND server_id=".$conf["server_id"]); |
fc70a2
|
318 |
$zones = array(); |
7fe908
|
319 |
|
957aaf
|
320 |
//* Check if the current zone that triggered this function has at least one NS record |
f038c0
|
321 |
/* Has been replaced by a better zone check |
531afa
|
322 |
$rec_num = $app->db->queryOneRecord("SELECT count(id) as ns FROM dns_rr WHERE type = 'NS' AND zone = ".intval($data['new']['id'])." AND active = 'Y'"); |
957aaf
|
323 |
if($rec_num['ns'] == 0) { |
T |
324 |
$exclude_zone = $data['new']['origin']; |
|
325 |
} else { |
|
326 |
$exclude_zone = ''; |
|
327 |
} |
f038c0
|
328 |
*/ |
7fe908
|
329 |
|
4b88c2
|
330 |
//TODO : change this when distribution information has been integrated into server record |
7fe908
|
331 |
if (file_exists('/etc/gentoo-release')) { |
MC |
332 |
$pri_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/pri/'; |
|
333 |
$sec_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/sec/'; |
|
334 |
|
|
335 |
} |
|
336 |
else { |
|
337 |
$pri_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/pri.'; |
|
338 |
$sec_zonefiles_path = $dns_config['bind_zonefiles_dir'].'/slave/sec.'; |
|
339 |
} |
a59ad3
|
340 |
|
957aaf
|
341 |
//* Loop trough zones |
fc70a2
|
342 |
foreach($tmps as $tmp) { |
7fe908
|
343 |
|
MC |
344 |
$zone_file = $pri_zonefiles_path.str_replace("/", "_", substr($tmp['origin'], 0, -1)); |
|
345 |
|
a59ad3
|
346 |
$options = ''; |
a59731
|
347 |
if(trim($tmp['xfer']) != '') { |
7fe908
|
348 |
$options .= " allow-transfer {".str_replace(',', ';', $tmp['xfer']).";};\n"; |
a59731
|
349 |
} else { |
D |
350 |
$options .= " allow-transfer {none;};\n"; |
|
351 |
} |
7fe908
|
352 |
if(trim($tmp['also_notify']) != '') $options .= ' also-notify {'.str_replace(',', ';', $tmp['also_notify']).";};\n"; |
MC |
353 |
if(trim($tmp['update_acl']) != '') $options .= " allow-update {".str_replace(',', ';', $tmp['update_acl']).";};\n"; |
|
354 |
|
f038c0
|
355 |
if(file_exists($zone_file)) { |
7fe908
|
356 |
$zones[] = array( 'zone' => substr($tmp['origin'], 0, -1), |
MC |
357 |
'zonefile_path' => $zone_file, |
|
358 |
'options' => $options |
|
359 |
); |
957aaf
|
360 |
} |
fc70a2
|
361 |
} |
a59731
|
362 |
|
7dbea0
|
363 |
$tpl = new tpl(); |
T |
364 |
$tpl->newTemplate("bind_named.conf.local.master"); |
7fe908
|
365 |
$tpl->setLoop('zones', $zones); |
MC |
366 |
|
a59731
|
367 |
//* And loop through the secondary zones, but only for the current server |
D |
368 |
$tmps_sec = $app->db->queryAllRecords("SELECT origin, xfer, ns FROM dns_slave WHERE active = 'Y' AND server_id=".$conf["server_id"]); |
|
369 |
$zones_sec = array(); |
|
370 |
|
|
371 |
foreach($tmps_sec as $tmp) { |
7fe908
|
372 |
|
a59731
|
373 |
$options = " masters {".$tmp['ns'].";};\n"; |
7fe908
|
374 |
if(trim($tmp['xfer']) != '') { |
MC |
375 |
$options .= " allow-transfer {".str_replace(',', ';', $tmp['xfer']).";};\n"; |
|
376 |
} else { |
|
377 |
$options .= " allow-transfer {none;};\n"; |
|
378 |
} |
a59731
|
379 |
|
D |
380 |
|
7fe908
|
381 |
$zones_sec[] = array( 'zone' => substr($tmp['origin'], 0, -1), |
MC |
382 |
'zonefile_path' => $sec_zonefiles_path.str_replace("/", "_", substr($tmp['origin'], 0, -1)), |
|
383 |
'options' => $options |
|
384 |
); |
a59731
|
385 |
|
7fe908
|
386 |
// $filename = escapeshellcmd($dns_config['bind_zonefiles_dir'].'/slave/sec.'.substr($tmp['origin'],0,-1)); |
MC |
387 |
// $app->log("Writing BIND domain file: ".$filename,LOGLEVEL_DEBUG); |
|
388 |
|
|
389 |
|
a59731
|
390 |
} |
7fe908
|
391 |
|
a59731
|
392 |
$tpl_sec = new tpl(); |
D |
393 |
$tpl_sec->newTemplate("bind_named.conf.local.slave"); |
7fe908
|
394 |
$tpl_sec->setLoop('zones', $zones_sec); |
a59731
|
395 |
|
7fe908
|
396 |
file_put_contents($dns_config['named_conf_local_path'], $tpl->grab()."\n".$tpl_sec->grab()); |
MC |
397 |
$app->log("Writing BIND named.conf.local file: ".$dns_config['named_conf_local_path'], LOGLEVEL_DEBUG); |
|
398 |
|
|
399 |
unset($tpl_sec); |
|
400 |
unset($zones_sec); |
|
401 |
unset($tmps_sec); |
7dbea0
|
402 |
unset($tpl); |
fc70a2
|
403 |
unset($zones); |
T |
404 |
unset($tmps); |
7fe908
|
405 |
|
7dbea0
|
406 |
} |
7fe908
|
407 |
|
MC |
408 |
|
|
409 |
|
7dbea0
|
410 |
|
T |
411 |
} // end class |
|
412 |
|
a59731
|
413 |
?> |