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