BIND
[hcoop/domtool2.git] / src / plugins / bind.sml
... / ...
CommitLineData
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
34val dns : TextIO.outstream option ref = ref NONE
35fun dnsF () = valOf (!dns)
36
37fun write s = TextIO.output (dnsF (), s)
38
39val _ = Domain.registerBefore
40 (fn _ => dns := SOME (Domain.domainFile "dns"))
41
42val _ = Domain.registerAfter
43 (fn _ => TextIO.closeOut (dnsF ()))
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
52
53val record = fn (EApp ((EApp ((EVar "dnsA", _), e1), _), e2), _) =>
54 (case (Env.string e1, Env.string e2) of
55 (SOME v1, SOME v2) => SOME (A (v1, v2))
56 | _ => NONE)
57 | (EApp ((EApp ((EVar "dnsCNAME", _), e1), _), e2), _) =>
58 (case (Env.string e1, Env.string e2) of
59 (SOME v1, SOME v2) => SOME (CNAME (v1, v2))
60 | _ => NONE)
61 | (EApp ((EApp ((EVar "dnsMX", _), e1), _), e2), _) =>
62 (case (Env.int e1, Env.string e2) of
63 (SOME v1, SOME v2) => SOME (MX (v1, v2))
64 | _ => NONE)
65 | (EApp ((EVar "dnsNS", _), e), _) =>
66 Option.map NS (Env.string e)
67 | _ => NONE
68
69fun writeRecord (evs, r) =
70 let
71 val ttl = Env.env Env.int (evs, "TTL")
72 in
73 case r of
74 A (from, to) => (write from;
75 write ".";
76 write (Domain.currentDomain ());
77 write ".\t";
78 write (Int.toString ttl);
79 write "\tIN\tA\t";
80 write to;
81 write "\n")
82 | CNAME (from, to) => (write from;
83 write ".";
84 write (Domain.currentDomain ());
85 write ".\t";
86 write (Int.toString ttl);
87 write "\tIN\tCNAME\t";
88 write to;
89 write ".\n")
90 | MX (num, host) => (write "\t";
91 write (Int.toString ttl);
92 write "\tIN\tMX\t";
93 write (Int.toString num);
94 write "\t";
95 write host;
96 write ".\n")
97 | NS host => (write "\t";
98 write (Int.toString ttl);
99 write "\tIN\tNS\t";
100 write host;
101 write ".\n")
102 end
103
104val () = Env.actionV_one "dns"
105 ("record", record)
106 writeRecord
107
108fun readLine inf =
109 case TextIO.inputLine inf of
110 NONE => raise Fail "Expected a line for BIND"
111 | SOME s => String.substring (s, 0, size s - 1)
112
113fun readILine inf = valOf (Int.fromString (readLine inf))
114
115val () = Slave.registerFileHandler (fn fs =>
116 let
117 val {dir, file} = OS.Path.splitDirFile (#file fs)
118
119 fun dnsChanged () =
120 if #domain fs = !didDomain then
121 ()
122 else if #action fs = Slave.Delete then
123 let
124 val fname = OS.Path.joinBaseExt {base = #domain fs,
125 ext = SOME "zone"}
126 val fname = OS.Path.joinDirFile {dir = Config.Bind.zonePath,
127 file = fname}
128 in
129 OS.FileSys.remove fname
130 end
131 else
132 let
133 val inf = TextIO.openIn (OS.Path.joinDirFile {dir = #dir fs,
134 file = "soa"})
135 val kind = readLine inf
136 val ttl = readILine inf
137 val ns = readLine inf
138 val serial = case readLine inf of
139 "" => NONE
140 | s => Int.fromString s
141 val rf = readILine inf
142 val ret = readILine inf
143 val exp = readILine inf
144 val min = readILine inf
145 val () = TextIO.closeIn inf
146
147 val dns = OS.Path.joinDirFile {dir = #dir fs,
148 file = "dns"}
149
150 val fname = OS.Path.joinBaseExt {base = #domain fs,
151 ext = SOME "zone"}
152 val fname = OS.Path.joinDirFile {dir = Config.Bind.zonePath,
153 file = fname}
154
155 val outf = TextIO.openOut fname
156 in
157 zoneChanged := true;
158 TextIO.output (outf, "$TTL ");
159 TextIO.output (outf, Int.toString ttl);
160 TextIO.output (outf, "\n\n@\tIN\tSOA\t");
161 TextIO.output (outf, ns);
162 TextIO.output (outf, ".\thostmaster.");
163 TextIO.output (outf, #domain fs);
164 TextIO.output (outf, ".\n( ");
165 TextIO.output (outf, Int.toString 123456789);
166 TextIO.output (outf, " ");
167 TextIO.output (outf, Int.toString rf);
168 TextIO.output (outf, " ");
169 TextIO.output (outf, Int.toString ret);
170 TextIO.output (outf, " ");
171 TextIO.output (outf, Int.toString exp);
172 TextIO.output (outf, " ");
173 TextIO.output (outf, Int.toString min);
174 TextIO.output (outf, " )\n\n");
175 TextIO.closeOut outf;
176 Slave.shellF ([Config.cat, " ", dns, " >>", fname],
177 fn cl => "Error concatenating file: " ^ cl);
178 didDomain := #domain fs
179 end
180 in
181 case file of
182 "soa" => dnsChanged ()
183 | "dns" => dnsChanged ()
184 | "named.conf" => namedChanged := true
185 | _ => ()
186 end)
187
188val () = Slave.registerPostHandler
189 (fn () =>
190 (if !namedChanged then
191 Slave.concatTo (fn s => s = "named.conf") Config.Bind.namedConf
192 else
193 ();
194 if !namedChanged orelse !zoneChanged then
195 Slave.shellF ([Config.Bind.reload],
196 fn cl => "Error reloading bind with " ^ cl)
197 else
198 ()))
199end