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