Add end_in_slash and use it to fix moinMoin; do extra reduction during evaluation
[hcoop/domtool2.git] / src / domain.sml
CommitLineData
a3698041 1(* HCoop Domtool (http://hcoop.sourceforge.net/)
3bf720f7 2 * Copyright (c) 2006-2007, Adam Chlipala
a3698041
AC
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dac62e84 17 *)
a3698041
AC
18
19(* Domain-related primitive actions *)
20
21structure Domain :> DOMAIN = struct
22
36e42cb8
AC
23open MsgTypes
24
2ed6d0e5 25structure SM = DataStructures.StringMap
12adf55a 26structure SS = DataStructures.StringSet
2ed6d0e5 27
36e42cb8
AC
28val ssl_context = ref (NONE : OpenSSL.context option)
29fun set_context ctx = ssl_context := SOME ctx
30
12adf55a 31val nodes = map #1 Config.nodeIps
2ed6d0e5
AC
32val nodeMap = foldl (fn ((node, ip), mp) => SM.insert (mp, node, ip))
33 SM.empty Config.nodeIps
8a7c40fa 34fun nodeIp node = valOf (SM.find (nodeMap, node))
2ed6d0e5 35
12adf55a 36val usr = ref ""
12adf55a
AC
37fun getUser () = !usr
38
39val your_doms = ref SS.empty
40fun your_domains () = !your_doms
41
8a7c40fa
AC
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
26c7d224
AC
51val your_ipss = ref SS.empty
52fun your_ips () = !your_ipss
53
998ed174 54val world_readable = SS.addList (SS.empty, Config.worldReadable)
70822196 55val readable_pths = ref world_readable
998ed174
AC
56fun readable_paths () = !readable_pths
57
aa56e112 58fun setUser user =
998ed174
AC
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;
26c7d224
AC
72 readable_pths := SS.union (your_paths, world_readable);
73 your_ipss := Acl.class {user = getUser (),
74 class = "ip"}
998ed174 75 end
aa56e112 76
6ae327f8
AC
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
629a34f6
AC
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
2ed6d0e5 93fun validNode s = List.exists (fn s' => s = s') nodes
e0b0abd2 94
12adf55a 95fun yourDomain s = SS.member (your_domains (), s)
8a7c40fa
AC
96fun yourUser s = SS.member (your_users (), s)
97fun yourGroup s = SS.member (your_groups (), s)
998ed174 98fun checkPath paths path =
8a7c40fa
AC
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
998ed174
AC
102 andalso SS.exists (fn s' => path = s' orelse String.isPrefix (s' ^ "/") path) (paths ())
103val yourPath = checkPath your_paths
104val readablePath = checkPath readable_paths
26c7d224 105fun yourIp s = SS.member (your_ips (), s)
12adf55a 106
edd38024 107fun yourDomainHost s =
c98b57cf
AC
108 yourDomain s
109 orelse let
edd38024
AC
110 val (pref, suf) = Substring.splitl (fn ch => ch <> #".") (Substring.full s)
111 in
edd38024
AC
112 Substring.size suf > 0
113 andalso validHost (Substring.string pref)
114 andalso yourDomain (Substring.string
c98b57cf 115 (Substring.slice (suf, 1, NONE)))
edd38024
AC
116 end
117
93c2f623
AC
118val yourDomain = yourDomainHost
119
2aeb9eec
AC
120fun validUser s = size s > 0 andalso size s < 20
121 andalso CharVector.all Char.isAlphaNum s
122
2e96b9d4
AC
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
2aeb9eec
AC
131val validGroup = validUser
132
f8dfbbcc
AC
133val _ = Env.type_one "no_spaces"
134 Env.string
ca6ffb3f
AC
135 (CharVector.all (fn ch => Char.isPrint ch andalso not (Char.isSpace ch)
136 andalso ch <> #"\"" andalso ch <> #"'"))
d5754b53
AC
137val _ = Env.type_one "no_newlines"
138 Env.string
ca6ffb3f
AC
139 (CharVector.all (fn ch => Char.isPrint ch andalso ch <> #"\n" andalso ch <> #"\r"
140 andalso ch <> #"\"" andalso ch <> #"'"))
f8dfbbcc 141
6ae327f8
AC
142val _ = Env.type_one "ip"
143 Env.string
144 validIp
145
629a34f6
AC
146val _ = Env.type_one "host"
147 Env.string
148 validHost
149
150val _ = Env.type_one "domain"
151 Env.string
152 validDomain
153
12adf55a
AC
154val _ = Env.type_one "your_domain"
155 Env.string
156 yourDomain
157
edd38024
AC
158val _ = Env.type_one "your_domain_host"
159 Env.string
160 yourDomainHost
161
2aeb9eec
AC
162val _ = Env.type_one "user"
163 Env.string
164 validUser
165
166val _ = Env.type_one "group"
167 Env.string
168 validGroup
169
8a7c40fa
AC
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
998ed174
AC
182val _ = Env.type_one "readable_path"
183 Env.string
184 readablePath
185
26c7d224
AC
186val _ = Env.type_one "your_ip"
187 Env.string
188 yourIp
189
e0b0abd2
AC
190val _ = Env.type_one "node"
191 Env.string
192 validNode
193
2e87719c
AC
194val _ = Env.registerFunction ("your_ip_to_ip",
195 fn [e] => SOME e
196 | _ => NONE)
197
bbdf617f
AC
198val _ = Env.registerFunction ("dns_node_to_node",
199 fn [e] => SOME e
200 | _ => NONE)
201
202val _ = Env.registerFunction ("mail_node_to_node",
be1bea4c
AC
203 fn [e] => SOME e
204 | _ => NONE)
b0963032
AC
205
206
a3698041
AC
207open Ast
208
6ae327f8
AC
209val dl = ErrorMsg.dummyLoc
210
b0963032
AC
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
6ae327f8
AC
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
e0b0abd2 232val masterD = (EApp ((EVar "internalMaster", dl),
8b84db5b 233 (EString Config.masterNode, dl)),
e0b0abd2
AC
234 dl)
235
8b84db5b
AC
236val slavesD = (EList (map (fn s => (EString s, dl)) Config.slaveNodes), dl)
237
e0b80e65
AC
238val _ = Defaults.registerDefault ("Aliases",
239 (TList (TBase "your_domain", dl), dl),
240 (fn () => (EList [], dl)))
241
6bb366c5
AC
242val _ = Defaults.registerDefault ("Mailbox",
243 (TBase "email", dl),
244 (fn () => (EString (getUser ()), dl)))
245
aa56e112
AC
246val _ = Defaults.registerDefault ("DNS",
247 (TBase "dnsKind", dl),
248 (fn () => multiApp ((EVar "useDns", dl),
249 dl,
8b84db5b 250 [soaD, masterD, slavesD])))
6ae327f8 251
aa56e112
AC
252val _ = Defaults.registerDefault ("TTL",
253 (TBase "int", dl),
254 (fn () => (EInt Config.Bind.defaultTTL, dl)))
6ae327f8
AC
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
e0b0abd2
AC
287datatype master =
288 ExternalMaster of string
289 | InternalMaster of string
290
cf879b4f
AC
291val ip = Env.string
292
293val _ = Env.registerFunction ("ip_of_node",
294 fn [(EString node, _)] => SOME (EString (nodeIp node), dl)
295 | _ => NONE)
97665758
AC
296
297val master = fn (EApp ((EVar "externalMaster", _), e), _) => Option.map ExternalMaster (ip e)
8b84db5b 298 | (EApp ((EVar "internalMaster", _), e), _) => Option.map InternalMaster (Env.string e)
e0b0abd2
AC
299 | _ => NONE
300
6ae327f8 301datatype dnsKind =
e0b0abd2
AC
302 UseDns of {soa : soa,
303 master : master,
304 slaves : string list}
6ae327f8
AC
305 | NoDns
306
e0b0abd2
AC
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)
325285ab 317 | (EVar "noDns", _) => SOME NoDns
6ae327f8
AC
318 | _ => NONE
319
a3698041
AC
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
71420f8b
AC
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
a3698041 358val current = ref ""
e0b0abd2 359val currentPath = ref (fn (_ : string) => "")
e0b80e65 360val currentPathAli = ref (fn (_ : string, _ : string) => "")
dac62e84 361
d612d62c
AC
362val scratch = ref ""
363
dac62e84
AC
364fun currentDomain () = !current
365
e0b80e65
AC
366val currentsAli = ref ([] : string list)
367
368fun currentAliasDomains () = !currentsAli
369fun currentDomains () = currentDomain () :: currentAliasDomains ()
370
e0b0abd2
AC
371fun domainFile {node, name} = ((*print ("Opening " ^ !currentPath node ^ name ^ "\n");*)
372 TextIO.openOut (!currentPath node ^ name))
dac62e84 373
e0b80e65
AC
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
dac62e84
AC
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
d612d62c 395
e0b0abd2
AC
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
2ed6d0e5 420 app doNode nodes;
dac62e84
AC
421 elems
422 end) [] toks
423 in
e0b0abd2 424 fn (root, site) => String.concatWith "/" (root :: site :: rev ("" :: elems))
d612d62c
AC
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
aaf70d45 432fun findDiffs (prefixes, site, dom, acts) =
d612d62c 433 let
e0b0abd2
AC
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")*)
d612d62c
AC
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
8df2e702 456 if Slave.shell [Config.diff, " ", real, " ", tmp] then
d612d62c
AC
457 loopReal acts
458 else
e0b0abd2 459 loopReal ((site, dom, realPath, Modify' {src = tmp, dst = real}) :: acts)
4f9c5b51 460 else if List.exists (fn prefix => String.isPrefix prefix real) prefixes then
e0b0abd2 461 loopReal ((site, dom, realPath, Delete' real) :: acts)
aaf70d45
AC
462 else
463 loopReal acts
d612d62c
AC
464 end
465
e0b0abd2 466 val acts = loopReal acts
d612d62c 467
8df2e702 468 val dir = Posix.FileSys.opendir tmpPath
d612d62c
AC
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
e0b0abd2 486 loopTmp ((site, dom, realPath, Add' {src = tmp, dst = real}) :: acts)
d612d62c
AC
487 end
488
489 val acts = loopTmp acts
490 in
491 acts
dac62e84 492 end
a3698041 493
aaf70d45 494fun findAllDiffs prefixes =
e0b0abd2
AC
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',
aaf70d45 526 findDiffs (prefixes, site, dom, diffs))
e0b0abd2
AC
527 end
528 else
529 diffs)
530 end
531 in
532 loop diffs
533 before Posix.FileSys.closedir dir
534 end
535 in
36e42cb8
AC
536 exploreSites (explore (OS.Path.joinDirFile {dir = Config.tmpDir,
537 file = site}, diffs))
e0b0abd2
AC
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
aaf70d45
AC
547val seenDomains : string list ref = ref []
548
6ae327f8
AC
549val _ = Env.containerV_one "domain"
550 ("domain", Env.string)
551 (fn (evs, dom) =>
552 let
aaf70d45
AC
553 val () = seenDomains := dom :: !seenDomains
554
6ae327f8
AC
555 val kind = Env.env dnsKind (evs, "DNS")
556 val ttl = Env.env Env.int (evs, "TTL")
e0b80e65 557 val aliases = Env.env (Env.list Env.string) (evs, "Aliases")
6ae327f8 558
e0b0abd2 559 val path = getPath dom
6ae327f8
AC
560
561 val () = (current := dom;
e0b80e65
AC
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)))
6ae327f8 565
e0b0abd2 566 fun saveSoa (kind, soa : soa) node =
e2359100 567 let
e0b80e65 568 val {write, writeDom, close} = domainsFile {node = node, name = "soa"}
e2359100 569 in
e0b80e65
AC
570 write kind;
571 write "\n";
572 write (Int.toString ttl);
573 write "\n";
574 write (#ns soa);
575 write "\n";
e2359100
AC
576 case #serial soa of
577 NONE => ()
e0b80e65
AC
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 ()
e2359100 589 end
6ae327f8 590
a431ca34 591 fun saveNamed (kind, soa : soa, masterIp, slaveIps) node =
b341fd6d
AC
592 if dom = "localhost" then
593 ()
594 else let
e0b80e65 595 val {write, writeDom, close} = domainsFile {node = node, name = "named.conf"}
b341fd6d 596 in
e0b80e65
AC
597 write "\nzone \"";
598 writeDom ();
a431ca34 599 write "\" {\n\ttype ";
e0b80e65
AC
600 write kind;
601 write ";\n\tfile \"";
602 write Config.Bind.zonePath_real;
603 write "/";
604 writeDom ();
605 write ".zone\";\n";
b341fd6d 606 case kind of
a431ca34
AC
607 "master" => (write "\tallow-transfer {\n";
608 app (fn ip => (write "\t\t";
609 write ip;
610 write ";\n")) slaveIps;
611 write "\t};\n")
e0b80e65
AC
612 | _ => (write "\tmasters { ";
613 write masterIp;
614 write "; };\n");
615 write "};\n";
616 close ()
b341fd6d 617 end
6ae327f8
AC
618 in
619 case kind of
e0b0abd2
AC
620 NoDns => masterNode := NONE
621 | UseDns dns =>
2ed6d0e5
AC
622 let
623 val masterIp =
624 case #master dns of
a431ca34 625 InternalMaster node => nodeIp node
2ed6d0e5 626 | ExternalMaster ip => ip
a431ca34
AC
627
628 val slaveIps = map nodeIp (#slaves dns)
2ed6d0e5
AC
629 in
630 app (saveSoa ("slave", #soa dns)) (#slaves dns);
a431ca34 631 app (saveNamed ("slave", #soa dns, masterIp, slaveIps)) (#slaves dns);
2ed6d0e5
AC
632 case #master dns of
633 InternalMaster node =>
634 (masterNode := SOME node;
635 saveSoa ("master", #soa dns) node;
a431ca34 636 saveNamed ("master", #soa dns, masterIp, slaveIps) node)
b25161c7
AC
637 | _ => masterNode := NONE
638 end;
639 !befores dom
6ae327f8 640 end,
e0b0abd2
AC
641 fn () => !afters (!current))
642
aaf70d45
AC
643val () = Env.registerPre (fn () => (seenDomains := [];
644 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
e0b0abd2
AC
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}))
2ed6d0e5 650 nodes;
e0b0abd2
AC
651 app (fn node => OS.FileSys.mkDir
652 (OS.Path.joinDirFile {dir = Config.resultRoot,
653 file = node})
654 handle OS.SysErr _ => ())
2ed6d0e5 655 nodes))
e0b0abd2 656
c189cbe9
AC
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
e0b0abd2
AC
686val () = Env.registerPost (fn () =>
687 let
4f9c5b51
AC
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))
aaf70d45
AC
698
699 val diffs = findAllDiffs prefixes
6ae327f8 700
e0b0abd2
AC
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,
6ae327f8
AC
705 {action = Slave.Add,
706 domain = dom,
707 dir = dir,
e0b0abd2
AC
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,
1638d5a2 714 {action = Slave.Delete true,
6ae327f8
AC
715 domain = dom,
716 dir = dir,
e0b0abd2
AC
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,
6ae327f8
AC
722 {action = Slave.Modify,
723 domain = dom,
724 dir = dir,
e0b0abd2
AC
725 file = dst}))) diffs
726 in
727 if !ErrorMsg.anyErrors then
728 ()
36e42cb8
AC
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
36e42cb8
AC
738 in
739 SM.appi handleSite changed
740 end;
e0b0abd2 741 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
559e89e9 742 fn cl => "Temp file cleanup failed: " ^ cl))
e0b0abd2 743 end)
6ae327f8 744
be1bea4c
AC
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))
60695e99 754
bbdf617f
AC
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
1638d5a2 762fun rmdom' delete resultRoot doms =
c189cbe9 763 let
c189cbe9
AC
764 fun doNode (node, _) =
765 let
1638d5a2 766 val dname = OS.Path.joinDirFile {dir = resultRoot,
c189cbe9 767 file = node}
c189cbe9 768
e69e60cc 769 fun doDom (dom, actions) =
93c2f623 770 let
e69e60cc
AC
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))
1638d5a2 790 else
1638d5a2
AC
791 loop ({action = Slave.Delete delete,
792 domain = dom,
793 dir = dname,
b200e996 794 file = fnameFull} :: actions)
e69e60cc
AC
795 end
796 in
797 loop actions
798 before Posix.FileSys.closedir dir
799 end
1638d5a2
AC
800 handle OS.SysErr (s, _) =>
801 (print ("Warning: System error deleting domain " ^ dom ^ " on " ^ node ^ ": " ^ s ^ "\n");
e69e60cc 802 actions)
93c2f623 803 in
e69e60cc 804 visitDom (dom, dname, actions)
93c2f623
AC
805 end
806
e69e60cc 807 val actions = foldl doDom [] doms
c189cbe9 808 in
c189cbe9
AC
809 handleSite (node, actions)
810 end
e69e60cc 811 handle IO.Io _ => print ("Warning: IO error deleting domains on " ^ node ^ ".\n")
c189cbe9
AC
812
813 fun cleanupNode (node, _) =
814 let
e69e60cc
AC
815 fun doDom dom =
816 let
817 val domPath = String.concatWith "/" (rev (String.fields (fn ch => ch = #".") dom))
1638d5a2 818 val dname = OS.Path.joinDirFile {dir = resultRoot,
e69e60cc
AC
819 file = node}
820 val dname = OS.Path.concat (dname, domPath)
821 in
1638d5a2
AC
822 if delete then
823 ignore (OS.Process.system (Config.rm ^ " -rf " ^ dname))
824 else
825 ()
e69e60cc 826 end
c189cbe9 827 in
e69e60cc 828 app doDom doms
c189cbe9
AC
829 end
830 in
831 app doNode Config.nodeIps;
832 app cleanupNode Config.nodeIps
833 end
834
1638d5a2
AC
835val rmdom = rmdom' true Config.resultRoot
836val rmdom' = rmdom' false
837
0da1c677
AC
838fun homedirOf uname =
839 Posix.SysDB.Passwd.home (Posix.SysDB.getpwnam uname)
840
841fun homedir () = homedirOf (getUser ())
842
314ce7bd
AC
843type subject = {node : string, domain : string}
844
845val describers : (subject -> string) list ref = ref []
846
847fun registerDescriber f = describers := f :: !describers
848
41c58daf 849fun describeOne arg = String.concat (map (fn f => f arg) (rev (!describers)))
314ce7bd 850
d300166d
AC
851val line = "--------------------------------------------------------------\n"
852val dline = "==============================================================\n"
314ce7bd
AC
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 =
41c58daf 864 Filename of { filename : string, heading : string, showEmpty : bool }
314ce7bd
AC
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 =>
41c58daf
AC
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
d936cf4d 909 entries := readFile showEmpty ("\n" :: line :: "\n" :: heading :: line :: !entries)
41c58daf
AC
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
d936cf4d 920 entries := readFile true ("\n" :: line :: "\n" :: heading base :: line :: !entries)
41c58daf
AC
921 else
922 ()
923 end
924 end) ds;
925 loop ())
314ce7bd
AC
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",
d936cf4d 936 heading = "DNS SOA:",
41c58daf 937 showEmpty = false}])
314ce7bd 938
e9f528ab
AC
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
a3698041 945end