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