Catch-all aliases working again
[hcoop/domtool2.git] / src / plugins / bind.sml
CommitLineData
6ae327f8
AC
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *)
18
19(* BIND DNS *)
20
21structure Bind :> BIND = struct
22
23open Ast
24
25val namedChanged = ref false
26val zoneChanged = ref false
27
28val didDomain = ref ""
29
30val () = Slave.registerPreHandler (fn () => (namedChanged := false;
31 zoneChanged := false;
32 didDomain := ""))
33
fa3b6b87 34val dns : Domain.files option ref = ref NONE
6ae327f8
AC
35
36val _ = Domain.registerBefore
fa3b6b87
AC
37 (fn _ => dns := Option.map (fn node => Domain.domainsFile {node = node,
38 name = "dns"})
e0b0abd2 39 (Domain.dnsMaster ()))
6ae327f8
AC
40
41val _ = Domain.registerAfter
fa3b6b87 42 (fn _ => (Option.app (fn files => #close files ()) (!dns);
325285ab 43 dns := NONE))
6ae327f8
AC
44
45val dl = ErrorMsg.dummyLoc
46
47datatype dns_record =
48 A of string * string
49 | CNAME of string * string
50 | MX of int * string
51 | NS of string
6eeb3bf9 52 | DefaultA of string
6ae327f8
AC
53
54val record = fn (EApp ((EApp ((EVar "dnsA", _), e1), _), e2), _) =>
97665758 55 (case (Env.string e1, Domain.ip e2) of
6ae327f8
AC
56 (SOME v1, SOME v2) => SOME (A (v1, v2))
57 | _ => NONE)
58 | (EApp ((EApp ((EVar "dnsCNAME", _), e1), _), e2), _) =>
59 (case (Env.string e1, Env.string e2) of
60 (SOME v1, SOME v2) => SOME (CNAME (v1, v2))
61 | _ => NONE)
62 | (EApp ((EApp ((EVar "dnsMX", _), e1), _), e2), _) =>
63 (case (Env.int e1, Env.string e2) of
64 (SOME v1, SOME v2) => SOME (MX (v1, v2))
65 | _ => NONE)
66 | (EApp ((EVar "dnsNS", _), e), _) =>
67 Option.map NS (Env.string e)
6eeb3bf9
AC
68 | (EApp ((EVar "dnsDefaultA", _), e), _) =>
69 Option.map DefaultA (Env.string e)
6ae327f8
AC
70 | _ => NONE
71
72fun writeRecord (evs, r) =
e0b0abd2 73 case !dns of
cf283351
AC
74 NONE => print ("Warning: DNS directive for " ^ Domain.currentDomain ()
75 ^ " ignored because no master DNS server is configured for this domain\n")
fa3b6b87 76 | SOME files =>
e0b0abd2 77 let
fa3b6b87
AC
78 fun write s = #write files s
79 fun writeDom () = #writeDom files ()
e0b0abd2
AC
80 val ttl = Env.env Env.int (evs, "TTL")
81 in
82 case r of
83 A (from, to) => (write from;
6ae327f8 84 write ".";
fa3b6b87 85 writeDom ();
6ae327f8
AC
86 write ".\t";
87 write (Int.toString ttl);
e0b0abd2 88 write "\tIN\tA\t";
6ae327f8 89 write to;
e0b0abd2 90 write "\n")
6eeb3bf9
AC
91 | DefaultA to => (writeDom ();
92 write ".\t";
93 write (Int.toString ttl);
94 write "\tIN\tA\t";
95 write to;
96 write "\n")
e0b0abd2
AC
97 | CNAME (from, to) => (write from;
98 write ".";
fa3b6b87 99 writeDom ();
e0b0abd2
AC
100 write ".\t";
101 write (Int.toString ttl);
102 write "\tIN\tCNAME\t";
103 write to;
104 write ".\n")
ec554091
AC
105 | MX (num, host) => (writeDom ();
106 write ".\t";
e0b0abd2
AC
107 write (Int.toString ttl);
108 write "\tIN\tMX\t";
109 write (Int.toString num);
110 write "\t";
111 write host;
112 write ".\n")
ec554091
AC
113 | NS host => (writeDom ();
114 write ".\t";
e0b0abd2
AC
115 write (Int.toString ttl);
116 write "\tIN\tNS\t";
117 write host;
118 write ".\n")
119 end
6ae327f8
AC
120
121val () = Env.actionV_one "dns"
122 ("record", record)
123 writeRecord
124
125fun readLine inf =
126 case TextIO.inputLine inf of
127 NONE => raise Fail "Expected a line for BIND"
128 | SOME s => String.substring (s, 0, size s - 1)
129
130fun readILine inf = valOf (Int.fromString (readLine inf))
131
3f07a40e
AC
132val monthToInt = fn Date.Jan => 1
133 | Date.Feb => 2
134 | Date.Mar => 3
135 | Date.Apr => 4
136 | Date.May => 5
137 | Date.Jun => 6
138 | Date.Jul => 7
139 | Date.Aug => 8
140 | Date.Sep => 9
141 | Date.Oct => 10
142 | Date.Nov => 11
143 | Date.Dec => 12
144
145fun padBy ch amt s =
146 if size s < amt then
82a0d609 147 CharVector.tabulate (amt - size s, fn _ => ch) ^ s
3f07a40e
AC
148 else
149 s
150
151fun dateString () =
152 let
153 val date = Date.fromTimeUniv (Time.now ())
154 in
155 padBy #"0" 4 (Int.toString (Date.year date))
156 ^ padBy #"0" 2 (Int.toString (monthToInt (Date.month date)))
157 ^ padBy #"0" 2 (Int.toString (Date.day date))
158 end
159
6ae327f8
AC
160val () = Slave.registerFileHandler (fn fs =>
161 let
162 val {dir, file} = OS.Path.splitDirFile (#file fs)
163
164 fun dnsChanged () =
165 if #domain fs = !didDomain then
166 ()
1638d5a2 167 else if Slave.isDelete (#action fs) then
6ae327f8
AC
168 let
169 val fname = OS.Path.joinBaseExt {base = #domain fs,
170 ext = SOME "zone"}
171 val fname = OS.Path.joinDirFile {dir = Config.Bind.zonePath,
172 file = fname}
173 in
325285ab
AC
174 Slave.shellF ([Config.rm, " -f ", fname],
175 fn cl => "Error deleting file: " ^ cl)
6ae327f8
AC
176 end
177 else
178 let
179 val inf = TextIO.openIn (OS.Path.joinDirFile {dir = #dir fs,
180 file = "soa"})
181 val kind = readLine inf
182 val ttl = readILine inf
183 val ns = readLine inf
184 val serial = case readLine inf of
185 "" => NONE
186 | s => Int.fromString s
187 val rf = readILine inf
188 val ret = readILine inf
189 val exp = readILine inf
190 val min = readILine inf
191 val () = TextIO.closeIn inf
192
82a0d609
AC
193 val serialPath = OS.Path.joinDirFile {dir = Config.serialDir,
194 file = #domain fs}
195
3f07a40e 196 val oldSerial = let
82a0d609 197 val inf = TextIO.openIn serialPath
3f07a40e
AC
198 in
199 SOME (readLine inf)
200 before TextIO.closeIn inf
82a0d609 201 end handle IO.Io {name, ...} => NONE
3f07a40e
AC
202
203 val newSerial =
204 case serial of
205 SOME n => Int.toString n
206 | NONE =>
207 let
208 val prefix = dateString ()
209 in
210 prefix
211 ^ (case oldSerial of
212 NONE => "00"
213 | SOME old =>
214 if size old >= 8 andalso
215 String.substring (old, 0, 8) = prefix then
216 case Int.fromString (String.extract (old, 8, NONE)) of
217 NONE => "00"
218 | SOME old => padBy #"0" 2 (Int.toString (old+1))
219 else
220 "00")
221 end
222
82a0d609 223 val outf = TextIO.openOut serialPath
3f07a40e
AC
224 val _ = TextIO.output (outf, newSerial)
225 val _ = TextIO.closeOut outf
226
6ae327f8
AC
227 val dns = OS.Path.joinDirFile {dir = #dir fs,
228 file = "dns"}
229
230 val fname = OS.Path.joinBaseExt {base = #domain fs,
231 ext = SOME "zone"}
232 val fname = OS.Path.joinDirFile {dir = Config.Bind.zonePath,
233 file = fname}
234
235 val outf = TextIO.openOut fname
236 in
237 zoneChanged := true;
238 TextIO.output (outf, "$TTL ");
239 TextIO.output (outf, Int.toString ttl);
240 TextIO.output (outf, "\n\n@\tIN\tSOA\t");
241 TextIO.output (outf, ns);
242 TextIO.output (outf, ".\thostmaster.");
243 TextIO.output (outf, #domain fs);
722e651b 244 TextIO.output (outf, ". ( ");
3f07a40e 245 TextIO.output (outf, newSerial);
6ae327f8
AC
246 TextIO.output (outf, " ");
247 TextIO.output (outf, Int.toString rf);
248 TextIO.output (outf, " ");
249 TextIO.output (outf, Int.toString ret);
250 TextIO.output (outf, " ");
251 TextIO.output (outf, Int.toString exp);
252 TextIO.output (outf, " ");
253 TextIO.output (outf, Int.toString min);
254 TextIO.output (outf, " )\n\n");
255 TextIO.closeOut outf;
325285ab
AC
256 if Posix.FileSys.access (dns, []) then
257 Slave.shellF ([Config.cat, " ", dns, " >>", fname],
258 fn cl => "Error concatenating file: " ^ cl)
259 else
260 ();
6ae327f8
AC
261 didDomain := #domain fs
262 end
263 in
264 case file of
265 "soa" => dnsChanged ()
266 | "dns" => dnsChanged ()
267 | "named.conf" => namedChanged := true
268 | _ => ()
269 end)
270
271val () = Slave.registerPostHandler
272 (fn () =>
273 (if !namedChanged then
274 Slave.concatTo (fn s => s = "named.conf") Config.Bind.namedConf
275 else
276 ();
277 if !namedChanged orelse !zoneChanged then
278 Slave.shellF ([Config.Bind.reload],
279 fn cl => "Error reloading bind with " ^ cl)
280 else
281 ()))
71420f8b
AC
282
283val () = Domain.registerResetLocal (fn () =>
284 ignore (OS.Process.system (Config.rm ^ " -rf /var/domtool/zones/*")))
285
41c58daf
AC
286val () = Domain.registerDescriber (Domain.considerAll
287 [Domain.Filename {filename = "named.conf",
d936cf4d 288 heading = "named.conf addition:",
41c58daf
AC
289 showEmpty = false},
290 Domain.Filename {filename = "dns",
d936cf4d 291 heading = "DNS zonefile contents:",
41c58daf
AC
292 showEmpty = false}])
293
6ae327f8 294end