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