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