vhost IP specification working
[hcoop/domtool2.git] / src / domain.sml
CommitLineData
a3698041 1(* HCoop Domtool (http://hcoop.sourceforge.net/)
3bf720f7 2 * Copyright (c) 2006-2007, Adam Chlipala
a3698041
AC
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.
dac62e84 17 *)
a3698041
AC
18
19(* Domain-related primitive actions *)
20
21structure Domain :> DOMAIN = struct
22
36e42cb8
AC
23open MsgTypes
24
2ed6d0e5 25structure SM = DataStructures.StringMap
12adf55a 26structure SS = DataStructures.StringSet
2ed6d0e5 27
36e42cb8
AC
28val ssl_context = ref (NONE : OpenSSL.context option)
29fun set_context ctx = ssl_context := SOME ctx
30
12adf55a 31val nodes = map #1 Config.nodeIps
2ed6d0e5
AC
32val nodeMap = foldl (fn ((node, ip), mp) => SM.insert (mp, node, ip))
33 SM.empty Config.nodeIps
8a7c40fa 34fun nodeIp node = valOf (SM.find (nodeMap, node))
2ed6d0e5 35
12adf55a 36val usr = ref ""
12adf55a
AC
37fun getUser () = !usr
38
39val your_doms = ref SS.empty
40fun your_domains () = !your_doms
41
8a7c40fa
AC
42val your_usrs = ref SS.empty
43fun your_users () = !your_usrs
44
45val your_grps = ref SS.empty
46fun your_groups () = !your_grps
47
48val your_pths = ref SS.empty
49fun your_paths () = !your_pths
50
26c7d224
AC
51val your_ipss = ref SS.empty
52fun your_ips () = !your_ipss
53
998ed174 54val world_readable = SS.addList (SS.empty, Config.worldReadable)
70822196 55val readable_pths = ref world_readable
998ed174
AC
56fun readable_paths () = !readable_pths
57
aa56e112 58fun setUser user =
998ed174
AC
59 let
60 val () = usr := user
61
62 val your_paths = Acl.class {user = getUser (),
63 class = "path"}
64 in
65 your_doms := Acl.class {user = getUser (),
66 class = "domain"};
67 your_usrs := Acl.class {user = getUser (),
68 class = "user"};
69 your_grps := Acl.class {user = getUser (),
70 class = "group"};
71 your_pths := your_paths;
26c7d224
AC
72 readable_pths := SS.union (your_paths, world_readable);
73 your_ipss := Acl.class {user = getUser (),
74 class = "ip"}
998ed174 75 end
aa56e112 76
6ae327f8
AC
77fun validIp s =
78 case map Int.fromString (String.fields (fn ch => ch = #".") s) of
79 [SOME n1, SOME n2, SOME n3, SOME n4] =>
80 n1 >= 0 andalso n1 < 256 andalso n2 >= 0 andalso n2 < 256 andalso n3 >= 0 andalso n3 < 256 andalso n4 >= 0 andalso n4 < 256
81 | _ => false
82
629a34f6
AC
83fun isIdent ch = Char.isLower ch orelse Char.isDigit ch
84
85fun validHost s =
86 size s > 0 andalso size s < 20
87 andalso CharVector.all (fn ch => isIdent ch orelse ch = #"-") s
88
89fun validDomain s =
90 size s > 0 andalso size s < 100
91 andalso List.all validHost (String.fields (fn ch => ch = #".") s)
92
2ed6d0e5 93fun validNode s = List.exists (fn s' => s = s') nodes
e0b0abd2 94
12adf55a 95fun yourDomain s = SS.member (your_domains (), s)
8a7c40fa
AC
96fun yourUser s = SS.member (your_users (), s)
97fun yourGroup s = SS.member (your_groups (), s)
998ed174 98fun checkPath paths path =
8a7c40fa
AC
99 List.all (fn s => s <> "..") (String.fields (fn ch => ch = #"/") path)
100 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"/"
101 orelse ch = #"-" orelse ch = #"_") path
998ed174
AC
102 andalso SS.exists (fn s' => path = s' orelse String.isPrefix (s' ^ "/") path) (paths ())
103val yourPath = checkPath your_paths
104val readablePath = checkPath readable_paths
26c7d224 105fun yourIp s = SS.member (your_ips (), s)
12adf55a 106
edd38024 107fun yourDomainHost s =
c98b57cf
AC
108 yourDomain s
109 orelse let
edd38024
AC
110 val (pref, suf) = Substring.splitl (fn ch => ch <> #".") (Substring.full s)
111 in
edd38024
AC
112 Substring.size suf > 0
113 andalso validHost (Substring.string pref)
114 andalso yourDomain (Substring.string
c98b57cf 115 (Substring.slice (suf, 1, NONE)))
edd38024
AC
116 end
117
93c2f623
AC
118val yourDomain = yourDomainHost
119
2aeb9eec
AC
120fun validUser s = size s > 0 andalso size s < 20
121 andalso CharVector.all Char.isAlphaNum s
122
2e96b9d4
AC
123fun validEmailUser s =
124 size s > 0 andalso size s < 50
125 andalso CharVector.all (fn ch => Char.isAlphaNum ch
126 orelse ch = #"."
127 orelse ch = #"_"
128 orelse ch = #"-"
129 orelse ch = #"+") s
130
2aeb9eec
AC
131val validGroup = validUser
132
f8dfbbcc
AC
133val _ = Env.type_one "no_spaces"
134 Env.string
ca6ffb3f
AC
135 (CharVector.all (fn ch => Char.isPrint ch andalso not (Char.isSpace ch)
136 andalso ch <> #"\"" andalso ch <> #"'"))
d5754b53
AC
137val _ = Env.type_one "no_newlines"
138 Env.string
ca6ffb3f
AC
139 (CharVector.all (fn ch => Char.isPrint ch andalso ch <> #"\n" andalso ch <> #"\r"
140 andalso ch <> #"\"" andalso ch <> #"'"))
f8dfbbcc 141
6ae327f8
AC
142val _ = Env.type_one "ip"
143 Env.string
144 validIp
145
629a34f6
AC
146val _ = Env.type_one "host"
147 Env.string
148 validHost
149
150val _ = Env.type_one "domain"
151 Env.string
152 validDomain
153
12adf55a
AC
154val _ = Env.type_one "your_domain"
155 Env.string
156 yourDomain
157
edd38024
AC
158val _ = Env.type_one "your_domain_host"
159 Env.string
160 yourDomainHost
161
2aeb9eec
AC
162val _ = Env.type_one "user"
163 Env.string
164 validUser
165
166val _ = Env.type_one "group"
167 Env.string
168 validGroup
169
8a7c40fa
AC
170val _ = Env.type_one "your_user"
171 Env.string
172 yourUser
173
174val _ = Env.type_one "your_group"
175 Env.string
176 yourGroup
177
178val _ = Env.type_one "your_path"
179 Env.string
180 yourPath
181
998ed174
AC
182val _ = Env.type_one "readable_path"
183 Env.string
184 readablePath
185
26c7d224
AC
186val _ = Env.type_one "your_ip"
187 Env.string
188 yourIp
189
e0b0abd2
AC
190val _ = Env.type_one "node"
191 Env.string
192 validNode
193
bbdf617f
AC
194val _ = Env.registerFunction ("dns_node_to_node",
195 fn [e] => SOME e
196 | _ => NONE)
197
198val _ = Env.registerFunction ("mail_node_to_node",
be1bea4c
AC
199 fn [e] => SOME e
200 | _ => NONE)
a3698041
AC
201open Ast
202
6ae327f8
AC
203val dl = ErrorMsg.dummyLoc
204
205val nsD = (EString Config.defaultNs, dl)
206val serialD = (EVar "serialAuto", dl)
207val refD = (EInt Config.defaultRefresh, dl)
208val retD = (EInt Config.defaultRetry, dl)
209val expD = (EInt Config.defaultExpiry, dl)
210val minD = (EInt Config.defaultMinimum, dl)
211
212val soaD = multiApp ((EVar "soa", dl),
213 dl,
214 [nsD, serialD, refD, retD, expD, minD])
215
e0b0abd2 216val masterD = (EApp ((EVar "internalMaster", dl),
8b84db5b 217 (EString Config.masterNode, dl)),
e0b0abd2
AC
218 dl)
219
8b84db5b
AC
220val slavesD = (EList (map (fn s => (EString s, dl)) Config.slaveNodes), dl)
221
e0b80e65
AC
222val _ = Defaults.registerDefault ("Aliases",
223 (TList (TBase "your_domain", dl), dl),
224 (fn () => (EList [], dl)))
225
6bb366c5
AC
226val _ = Defaults.registerDefault ("Mailbox",
227 (TBase "email", dl),
228 (fn () => (EString (getUser ()), dl)))
229
aa56e112
AC
230val _ = Defaults.registerDefault ("DNS",
231 (TBase "dnsKind", dl),
232 (fn () => multiApp ((EVar "useDns", dl),
233 dl,
8b84db5b 234 [soaD, masterD, slavesD])))
6ae327f8 235
aa56e112
AC
236val _ = Defaults.registerDefault ("TTL",
237 (TBase "int", dl),
238 (fn () => (EInt Config.Bind.defaultTTL, dl)))
6ae327f8
AC
239
240type soa = {ns : string,
241 serial : int option,
242 ref : int,
243 ret : int,
244 exp : int,
245 min : int}
246
247val serial = fn (EVar "serialAuto", _) => SOME NONE
248 | (EApp ((EVar "serialConst", _), n), _) => Option.map SOME (Env.int n)
249 | _ => NONE
250
251val soa = fn (EApp ((EApp ((EApp ((EApp ((EApp ((EApp
252 ((EVar "soa", _), ns), _),
253 sl), _),
254 rf), _),
255 ret), _),
256 exp), _),
257 min), _) =>
258 (case (Env.string ns, serial sl, Env.int rf,
259 Env.int ret, Env.int exp, Env.int min) of
260 (SOME ns, SOME sl, SOME rf,
261 SOME ret, SOME exp, SOME min) =>
262 SOME {ns = ns,
263 serial = sl,
264 ref = rf,
265 ret = ret,
266 exp = exp,
267 min = min}
268 | _ => NONE)
269 | _ => NONE
270
e0b0abd2
AC
271datatype master =
272 ExternalMaster of string
273 | InternalMaster of string
274
cf879b4f
AC
275val ip = Env.string
276
277val _ = Env.registerFunction ("ip_of_node",
278 fn [(EString node, _)] => SOME (EString (nodeIp node), dl)
279 | _ => NONE)
97665758
AC
280
281val master = fn (EApp ((EVar "externalMaster", _), e), _) => Option.map ExternalMaster (ip e)
8b84db5b 282 | (EApp ((EVar "internalMaster", _), e), _) => Option.map InternalMaster (Env.string e)
e0b0abd2
AC
283 | _ => NONE
284
6ae327f8 285datatype dnsKind =
e0b0abd2
AC
286 UseDns of {soa : soa,
287 master : master,
288 slaves : string list}
6ae327f8
AC
289 | NoDns
290
e0b0abd2
AC
291val dnsKind = fn (EApp ((EApp ((EApp
292 ((EVar "useDns", _), sa), _),
293 mstr), _),
294 slaves), _) =>
295 (case (soa sa, master mstr, Env.list Env.string slaves) of
296 (SOME sa, SOME mstr, SOME slaves) =>
297 SOME (UseDns {soa = sa,
298 master = mstr,
299 slaves = slaves})
300 | _ => NONE)
325285ab 301 | (EVar "noDns", _) => SOME NoDns
6ae327f8
AC
302 | _ => NONE
303
a3698041
AC
304val befores = ref (fn (_ : string) => ())
305val afters = ref (fn (_ : string) => ())
306
307fun registerBefore f =
308 let
309 val old = !befores
310 in
311 befores := (fn x => (old x; f x))
312 end
313
314fun registerAfter f =
315 let
316 val old = !afters
317 in
318 afters := (fn x => (old x; f x))
319 end
320
71420f8b
AC
321val globals = ref (fn () => ())
322val locals = ref (fn () => ())
323
324fun registerResetGlobal f =
325 let
326 val old = !globals
327 in
328 globals := (fn x => (old x; f x))
329 end
330
331fun registerResetLocal f =
332 let
333 val old = !locals
334 in
335 locals := (fn x => (old x; f x))
336 end
337
338fun resetGlobal () = (!globals ();
339 ignore (OS.Process.system (Config.rm ^ " -rf " ^ Config.resultRoot ^ "/*")))
340fun resetLocal () = !locals ()
341
a3698041 342val current = ref ""
e0b0abd2 343val currentPath = ref (fn (_ : string) => "")
e0b80e65 344val currentPathAli = ref (fn (_ : string, _ : string) => "")
dac62e84 345
d612d62c
AC
346val scratch = ref ""
347
dac62e84
AC
348fun currentDomain () = !current
349
e0b80e65
AC
350val currentsAli = ref ([] : string list)
351
352fun currentAliasDomains () = !currentsAli
353fun currentDomains () = currentDomain () :: currentAliasDomains ()
354
e0b0abd2
AC
355fun domainFile {node, name} = ((*print ("Opening " ^ !currentPath node ^ name ^ "\n");*)
356 TextIO.openOut (!currentPath node ^ name))
dac62e84 357
e0b80e65
AC
358type files = {write : string -> unit,
359 writeDom : unit -> unit,
360 close : unit -> unit}
361
362fun domainsFile {node, name} =
363 let
364 val doms = currentDomains ()
365 val files = map (fn dom => (dom, TextIO.openOut (!currentPathAli (dom, node) ^ name))) doms
366 in
367 {write = fn s => app (fn (_, outf) => TextIO.output (outf, s)) files,
368 writeDom = fn () => app (fn (dom, outf) => TextIO.output (outf, dom)) files,
369 close = fn () => app (fn (_, outf) => TextIO.closeOut outf) files}
370 end
371
dac62e84
AC
372fun getPath domain =
373 let
374 val toks = String.fields (fn ch => ch = #".") domain
375
376 val elems = foldr (fn (piece, elems) =>
377 let
378 val elems = piece :: elems
d612d62c 379
e0b0abd2
AC
380 fun doNode node =
381 let
382 val path = String.concatWith "/"
383 (Config.resultRoot :: node :: rev elems)
384 val tmpPath = String.concatWith "/"
385 (Config.tmpDir :: node :: rev elems)
386 in
387 (if Posix.FileSys.ST.isDir
388 (Posix.FileSys.stat path) then
389 ()
390 else
391 (OS.FileSys.remove path;
392 OS.FileSys.mkDir path))
393 handle OS.SysErr _ => OS.FileSys.mkDir path;
394
395 (if Posix.FileSys.ST.isDir
396 (Posix.FileSys.stat tmpPath) then
397 ()
398 else
399 (OS.FileSys.remove tmpPath;
400 OS.FileSys.mkDir tmpPath))
401 handle OS.SysErr _ => OS.FileSys.mkDir tmpPath
402 end
403 in
2ed6d0e5 404 app doNode nodes;
dac62e84
AC
405 elems
406 end) [] toks
407 in
e0b0abd2 408 fn (root, site) => String.concatWith "/" (root :: site :: rev ("" :: elems))
d612d62c
AC
409 end
410
411datatype file_action' =
412 Add' of {src : string, dst : string}
413 | Delete' of string
414 | Modify' of {src : string, dst : string}
415
aaf70d45 416fun findDiffs (prefixes, site, dom, acts) =
d612d62c 417 let
e0b0abd2
AC
418 val gp = getPath dom
419 val realPath = gp (Config.resultRoot, site)
420 val tmpPath = gp (Config.tmpDir, site)
421
422 (*val _ = print ("getDiffs(" ^ site ^ ", " ^ dom ^ ")... " ^ realPath ^ "; " ^ tmpPath ^ "\n")*)
d612d62c
AC
423
424 val dir = Posix.FileSys.opendir realPath
425
426 fun loopReal acts =
427 case Posix.FileSys.readdir dir of
428 NONE => (Posix.FileSys.closedir dir;
429 acts)
430 | SOME fname =>
431 let
432 val real = OS.Path.joinDirFile {dir = realPath,
433 file = fname}
434 val tmp = OS.Path.joinDirFile {dir = tmpPath,
435 file = fname}
436 in
437 if Posix.FileSys.ST.isDir (Posix.FileSys.stat real) then
438 loopReal acts
439 else if Posix.FileSys.access (tmp, []) then
8df2e702 440 if Slave.shell [Config.diff, " ", real, " ", tmp] then
d612d62c
AC
441 loopReal acts
442 else
e0b0abd2 443 loopReal ((site, dom, realPath, Modify' {src = tmp, dst = real}) :: acts)
4f9c5b51 444 else if List.exists (fn prefix => String.isPrefix prefix real) prefixes then
e0b0abd2 445 loopReal ((site, dom, realPath, Delete' real) :: acts)
aaf70d45
AC
446 else
447 loopReal acts
d612d62c
AC
448 end
449
e0b0abd2 450 val acts = loopReal acts
d612d62c 451
8df2e702 452 val dir = Posix.FileSys.opendir tmpPath
d612d62c
AC
453
454 fun loopTmp acts =
455 case Posix.FileSys.readdir dir of
456 NONE => (Posix.FileSys.closedir dir;
457 acts)
458 | SOME fname =>
459 let
460 val real = OS.Path.joinDirFile {dir = realPath,
461 file = fname}
462 val tmp = OS.Path.joinDirFile {dir = tmpPath,
463 file = fname}
464 in
465 if Posix.FileSys.ST.isDir (Posix.FileSys.stat tmp) then
466 loopTmp acts
467 else if Posix.FileSys.access (real, []) then
468 loopTmp acts
469 else
e0b0abd2 470 loopTmp ((site, dom, realPath, Add' {src = tmp, dst = real}) :: acts)
d612d62c
AC
471 end
472
473 val acts = loopTmp acts
474 in
475 acts
dac62e84 476 end
a3698041 477
aaf70d45 478fun findAllDiffs prefixes =
e0b0abd2
AC
479 let
480 val dir = Posix.FileSys.opendir Config.tmpDir
481 val len = length (String.fields (fn ch => ch = #"/") Config.tmpDir) + 1
482
483 fun exploreSites diffs =
484 case Posix.FileSys.readdir dir of
485 NONE => diffs
486 | SOME site =>
487 let
488 fun explore (dname, diffs) =
489 let
490 val dir = Posix.FileSys.opendir dname
491
492 fun loop diffs =
493 case Posix.FileSys.readdir dir of
494 NONE => diffs
495 | SOME name =>
496 let
497 val fname = OS.Path.joinDirFile {dir = dname,
498 file = name}
499 in
500 loop (if Posix.FileSys.ST.isDir (Posix.FileSys.stat fname) then
501 let
502 val dom = String.fields (fn ch => ch = #"/") fname
503 val dom = List.drop (dom, len)
504 val dom = String.concatWith "." (rev dom)
505
506 val dname' = OS.Path.joinDirFile {dir = dname,
507 file = name}
508 in
509 explore (dname',
aaf70d45 510 findDiffs (prefixes, site, dom, diffs))
e0b0abd2
AC
511 end
512 else
513 diffs)
514 end
515 in
516 loop diffs
517 before Posix.FileSys.closedir dir
518 end
519 in
36e42cb8
AC
520 exploreSites (explore (OS.Path.joinDirFile {dir = Config.tmpDir,
521 file = site}, diffs))
e0b0abd2
AC
522 end
523 in
524 exploreSites []
525 before Posix.FileSys.closedir dir
526 end
527
528val masterNode : string option ref = ref NONE
529fun dnsMaster () = !masterNode
530
aaf70d45
AC
531val seenDomains : string list ref = ref []
532
6ae327f8
AC
533val _ = Env.containerV_one "domain"
534 ("domain", Env.string)
535 (fn (evs, dom) =>
536 let
aaf70d45
AC
537 val () = seenDomains := dom :: !seenDomains
538
6ae327f8
AC
539 val kind = Env.env dnsKind (evs, "DNS")
540 val ttl = Env.env Env.int (evs, "TTL")
e0b80e65 541 val aliases = Env.env (Env.list Env.string) (evs, "Aliases")
6ae327f8 542
e0b0abd2 543 val path = getPath dom
6ae327f8
AC
544
545 val () = (current := dom;
e0b80e65
AC
546 currentsAli := Slave.remove (Slave.removeDups aliases, dom);
547 currentPath := (fn site => path (Config.tmpDir, site));
548 currentPathAli := (fn (dom, site) => getPath dom (Config.tmpDir, site)))
6ae327f8 549
e0b0abd2 550 fun saveSoa (kind, soa : soa) node =
e2359100 551 let
e0b80e65 552 val {write, writeDom, close} = domainsFile {node = node, name = "soa"}
e2359100 553 in
e0b80e65
AC
554 write kind;
555 write "\n";
556 write (Int.toString ttl);
557 write "\n";
558 write (#ns soa);
559 write "\n";
e2359100
AC
560 case #serial soa of
561 NONE => ()
e0b80e65
AC
562 | SOME n => write (Int.toString n);
563 write "\n";
564 write (Int.toString (#ref soa));
565 write "\n";
566 write (Int.toString (#ret soa));
567 write "\n";
568 write (Int.toString (#exp soa));
569 write "\n";
570 write (Int.toString (#min soa));
571 write "\n";
572 close ()
e2359100 573 end
6ae327f8 574
a431ca34 575 fun saveNamed (kind, soa : soa, masterIp, slaveIps) node =
b341fd6d
AC
576 if dom = "localhost" then
577 ()
578 else let
e0b80e65 579 val {write, writeDom, close} = domainsFile {node = node, name = "named.conf"}
b341fd6d 580 in
e0b80e65
AC
581 write "\nzone \"";
582 writeDom ();
a431ca34 583 write "\" {\n\ttype ";
e0b80e65
AC
584 write kind;
585 write ";\n\tfile \"";
586 write Config.Bind.zonePath_real;
587 write "/";
588 writeDom ();
589 write ".zone\";\n";
b341fd6d 590 case kind of
a431ca34
AC
591 "master" => (write "\tallow-transfer {\n";
592 app (fn ip => (write "\t\t";
593 write ip;
594 write ";\n")) slaveIps;
595 write "\t};\n")
e0b80e65
AC
596 | _ => (write "\tmasters { ";
597 write masterIp;
598 write "; };\n");
599 write "};\n";
600 close ()
b341fd6d 601 end
6ae327f8
AC
602 in
603 case kind of
e0b0abd2
AC
604 NoDns => masterNode := NONE
605 | UseDns dns =>
2ed6d0e5
AC
606 let
607 val masterIp =
608 case #master dns of
a431ca34 609 InternalMaster node => nodeIp node
2ed6d0e5 610 | ExternalMaster ip => ip
a431ca34
AC
611
612 val slaveIps = map nodeIp (#slaves dns)
2ed6d0e5
AC
613 in
614 app (saveSoa ("slave", #soa dns)) (#slaves dns);
a431ca34 615 app (saveNamed ("slave", #soa dns, masterIp, slaveIps)) (#slaves dns);
2ed6d0e5
AC
616 case #master dns of
617 InternalMaster node =>
618 (masterNode := SOME node;
619 saveSoa ("master", #soa dns) node;
a431ca34 620 saveNamed ("master", #soa dns, masterIp, slaveIps) node)
b25161c7
AC
621 | _ => masterNode := NONE
622 end;
623 !befores dom
6ae327f8 624 end,
e0b0abd2
AC
625 fn () => !afters (!current))
626
aaf70d45
AC
627val () = Env.registerPre (fn () => (seenDomains := [];
628 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
e0b0abd2
AC
629 fn cl => "Temp file cleanup failed: " ^ cl));
630 OS.FileSys.mkDir Config.tmpDir;
631 app (fn node => OS.FileSys.mkDir
632 (OS.Path.joinDirFile {dir = Config.tmpDir,
633 file = node}))
2ed6d0e5 634 nodes;
e0b0abd2
AC
635 app (fn node => OS.FileSys.mkDir
636 (OS.Path.joinDirFile {dir = Config.resultRoot,
637 file = node})
638 handle OS.SysErr _ => ())
2ed6d0e5 639 nodes))
e0b0abd2 640
c189cbe9
AC
641fun handleSite (site, files) =
642 let
643
644 in
645 print ("New configuration for node " ^ site ^ "\n");
646 if site = Config.defaultNode then
647 Slave.handleChanges files
648 else let
649 val bio = OpenSSL.connect (valOf (!ssl_context),
650 nodeIp site
651 ^ ":"
652 ^ Int.toString Config.slavePort)
653 in
654 app (fn file => Msg.send (bio, MsgFile file)) files;
655 Msg.send (bio, MsgDoFiles);
656 case Msg.recv bio of
657 NONE => print "Slave closed connection unexpectedly\n"
658 | SOME m =>
659 case m of
660 MsgOk => print ("Slave " ^ site ^ " finished\n")
661 | MsgError s => print ("Slave " ^ site
662 ^ " returned error: " ^
663 s ^ "\n")
664 | _ => print ("Slave " ^ site
665 ^ " returned unexpected command\n");
666 OpenSSL.close bio
667 end
668 end
669
e0b0abd2
AC
670val () = Env.registerPost (fn () =>
671 let
4f9c5b51
AC
672 val prefixes = List.concat
673 (List.map (fn dom =>
674 let
675 val pieces = String.tokens (fn ch => ch = #".") dom
676 val path = String.concatWith "/" (rev pieces)
677 in
678 List.map (fn node =>
679 Config.resultRoot ^ "/" ^ node ^ "/" ^ path ^ "/")
680 nodes
681 end) (!seenDomains))
aaf70d45
AC
682
683 val diffs = findAllDiffs prefixes
6ae327f8 684
e0b0abd2
AC
685 val diffs = map (fn (site, dom, dir, Add' {src, dst}) =>
686 (Slave.shellF ([Config.cp, " ", src, " ", dst],
687 fn cl => "Copy failed: " ^ cl);
688 (site,
6ae327f8
AC
689 {action = Slave.Add,
690 domain = dom,
691 dir = dir,
e0b0abd2
AC
692 file = dst}))
693 | (site, dom, dir, Delete' dst) =>
694 (OS.FileSys.remove dst
695 handle OS.SysErr _ =>
696 ErrorMsg.error NONE ("Delete failed for " ^ dst);
697 (site,
1638d5a2 698 {action = Slave.Delete true,
6ae327f8
AC
699 domain = dom,
700 dir = dir,
e0b0abd2
AC
701 file = dst}))
702 | (site, dom, dir, Modify' {src, dst}) =>
703 (Slave.shellF ([Config.cp, " ", src, " ", dst],
704 fn cl => "Copy failed: " ^ cl);
705 (site,
6ae327f8
AC
706 {action = Slave.Modify,
707 domain = dom,
708 dir = dir,
e0b0abd2
AC
709 file = dst}))) diffs
710 in
711 if !ErrorMsg.anyErrors then
712 ()
36e42cb8
AC
713 else let
714 val changed = foldl (fn ((site, file), changed) =>
715 let
716 val ls = case SM.find (changed, site) of
717 NONE => []
718 | SOME ls => ls
719 in
720 SM.insert (changed, site, file :: ls)
721 end) SM.empty diffs
36e42cb8
AC
722 in
723 SM.appi handleSite changed
724 end;
e0b0abd2 725 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
559e89e9 726 fn cl => "Temp file cleanup failed: " ^ cl))
e0b0abd2 727 end)
6ae327f8 728
be1bea4c
AC
729fun hasPriv priv = Acl.query {user = getUser (), class = "priv", value = "all"}
730 orelse Acl.query {user = getUser (), class = "priv", value = priv}
731
732val _ = Env.type_one "dns_node"
733 Env.string
734 (fn node =>
735 List.exists (fn x => x = node) Config.dnsNodes_all
736 orelse (hasPriv "dns"
737 andalso List.exists (fn x => x = node) Config.dnsNodes_admin))
60695e99 738
bbdf617f
AC
739val _ = Env.type_one "mail_node"
740 Env.string
741 (fn node =>
742 List.exists (fn x => x = node) Config.mailNodes_all
743 orelse (hasPriv "mail"
744 andalso List.exists (fn x => x = node) Config.mailNodes_admin))
745
1638d5a2 746fun rmdom' delete resultRoot doms =
c189cbe9 747 let
c189cbe9
AC
748 fun doNode (node, _) =
749 let
1638d5a2 750 val dname = OS.Path.joinDirFile {dir = resultRoot,
c189cbe9 751 file = node}
c189cbe9 752
e69e60cc 753 fun doDom (dom, actions) =
93c2f623 754 let
e69e60cc
AC
755 val domPath = String.concatWith "/" (rev (String.fields (fn ch => ch = #".") dom))
756 val dname = OS.Path.concat (dname, domPath)
757
758 fun visitDom (dom, dname, actions) =
759 let
760 val dir = Posix.FileSys.opendir dname
761
762 fun loop actions =
763 case Posix.FileSys.readdir dir of
764 NONE => actions
765 | SOME fname =>
766 let
767 val fnameFull = OS.Path.joinDirFile {dir = dname,
768 file = fname}
769 in
770 if Posix.FileSys.ST.isDir (Posix.FileSys.stat fnameFull) then
771 loop (visitDom (fname ^ "." ^ dom,
772 fnameFull,
773 actions))
1638d5a2 774 else
1638d5a2
AC
775 loop ({action = Slave.Delete delete,
776 domain = dom,
777 dir = dname,
b200e996 778 file = fnameFull} :: actions)
e69e60cc
AC
779 end
780 in
781 loop actions
782 before Posix.FileSys.closedir dir
783 end
1638d5a2
AC
784 handle OS.SysErr (s, _) =>
785 (print ("Warning: System error deleting domain " ^ dom ^ " on " ^ node ^ ": " ^ s ^ "\n");
e69e60cc 786 actions)
93c2f623 787 in
e69e60cc 788 visitDom (dom, dname, actions)
93c2f623
AC
789 end
790
e69e60cc 791 val actions = foldl doDom [] doms
c189cbe9 792 in
c189cbe9
AC
793 handleSite (node, actions)
794 end
e69e60cc 795 handle IO.Io _ => print ("Warning: IO error deleting domains on " ^ node ^ ".\n")
c189cbe9
AC
796
797 fun cleanupNode (node, _) =
798 let
e69e60cc
AC
799 fun doDom dom =
800 let
801 val domPath = String.concatWith "/" (rev (String.fields (fn ch => ch = #".") dom))
1638d5a2 802 val dname = OS.Path.joinDirFile {dir = resultRoot,
e69e60cc
AC
803 file = node}
804 val dname = OS.Path.concat (dname, domPath)
805 in
1638d5a2
AC
806 if delete then
807 ignore (OS.Process.system (Config.rm ^ " -rf " ^ dname))
808 else
809 ()
e69e60cc 810 end
c189cbe9 811 in
e69e60cc 812 app doDom doms
c189cbe9
AC
813 end
814 in
815 app doNode Config.nodeIps;
816 app cleanupNode Config.nodeIps
817 end
818
1638d5a2
AC
819val rmdom = rmdom' true Config.resultRoot
820val rmdom' = rmdom' false
821
0da1c677
AC
822fun homedirOf uname =
823 Posix.SysDB.Passwd.home (Posix.SysDB.getpwnam uname)
824
825fun homedir () = homedirOf (getUser ())
826
314ce7bd
AC
827type subject = {node : string, domain : string}
828
829val describers : (subject -> string) list ref = ref []
830
831fun registerDescriber f = describers := f :: !describers
832
41c58daf 833fun describeOne arg = String.concat (map (fn f => f arg) (rev (!describers)))
314ce7bd 834
d300166d
AC
835val line = "--------------------------------------------------------------\n"
836val dline = "==============================================================\n"
314ce7bd
AC
837
838fun describe dom =
839 String.concat (List.mapPartial
840 (fn node =>
841 case describeOne {node = node, domain = dom} of
842 "" => NONE
843 | s =>
844 SOME (String.concat [dline, "Node ", node, "\n", dline, "\n", s]))
845 nodes)
846
847datatype description =
41c58daf 848 Filename of { filename : string, heading : string, showEmpty : bool }
314ce7bd
AC
849 | Extension of { extension : string, heading : string -> string }
850
851fun considerAll ds {node, domain} =
852 let
853 val ds = map (fn d => (d, ref [])) ds
854
855 val path = Config.resultRoot
856 val jdf = OS.Path.joinDirFile
857 val path = jdf {dir = path, file = node}
858 val path = foldr (fn (more, path) => jdf {dir = path, file = more})
859 path (String.tokens (fn ch => ch = #".") domain)
860 in
861 if Posix.FileSys.access (path, []) then
862 let
863 val dir = Posix.FileSys.opendir path
864
865 fun loop () =
866 case Posix.FileSys.readdir dir of
867 NONE => ()
868 | SOME fname =>
41c58daf
AC
869 (app (fn (d, entries) =>
870 let
871 fun readFile showEmpty entries' =
872 let
873 val fname = OS.Path.joinDirFile {dir = path,
874 file = fname}
875
876 val inf = TextIO.openIn fname
877
878 fun loop (seenOne, entries') =
879 case TextIO.inputLine inf of
880 NONE => if seenOne orelse showEmpty then
881 "\n" :: entries'
882 else
883 !entries
884 | SOME line => loop (true, line :: entries')
885 in
886 loop (false, entries')
887 before TextIO.closeIn inf
888 end
889 in
890 case d of
891 Filename {filename, heading, showEmpty} =>
892 if fname = filename then
893 entries := readFile showEmpty ("\n" :: line :: ":\n" :: heading :: line :: !entries)
894 else
895 ()
896 | Extension {extension, heading} =>
897 let
898 val {base, ext} = OS.Path.splitBaseExt fname
899 in
900 case ext of
901 NONE => ()
902 | SOME extension' =>
903 if extension' = extension then
904 entries := readFile true ("\n" :: line :: ":\n" :: heading base :: line :: !entries)
905 else
906 ()
907 end
908 end) ds;
909 loop ())
314ce7bd
AC
910 in
911 loop ();
912 Posix.FileSys.closedir dir;
913 String.concat (List.concat (map (fn (_, entries) => rev (!entries)) ds))
914 end
915 else
916 ""
917 end
918
919val () = registerDescriber (considerAll [Filename {filename = "soa",
41c58daf
AC
920 heading = "DNS SOA",
921 showEmpty = false}])
314ce7bd 922
e9f528ab
AC
923val () = Env.registerAction ("domainHost",
924 fn (env, [(EString host, _)]) =>
925 SM.insert (env, "Hostname",
926 (EString (host ^ "." ^ currentDomain ()), dl))
927 | (_, args) => Env.badArgs ("domainHost", args))
928
a3698041 929end