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