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