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