Changes to support IMAP on hopper all compile but are not tested yet
[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
2ed6d0e5
AC
33val nodeMap = foldl (fn ((node, ip), mp) => SM.insert (mp, node, ip))
34 SM.empty Config.nodeIps
8a7c40fa 35fun nodeIp node = valOf (SM.find (nodeMap, node))
2ed6d0e5 36
12adf55a 37val usr = ref ""
12adf55a 38fun getUser () = !usr
04502362
AC
39val fakePrivs = ref false
40val isClient = ref false
12adf55a
AC
41
42val your_doms = ref SS.empty
43fun your_domains () = !your_doms
44
8a7c40fa
AC
45val your_usrs = ref SS.empty
46fun your_users () = !your_usrs
47
48val your_grps = ref SS.empty
49fun your_groups () = !your_grps
50
51val your_pths = ref SS.empty
52fun your_paths () = !your_pths
53
26c7d224
AC
54val your_ipss = ref SS.empty
55fun your_ips () = !your_ipss
56
998ed174 57val world_readable = SS.addList (SS.empty, Config.worldReadable)
70822196 58val readable_pths = ref world_readable
998ed174
AC
59fun readable_paths () = !readable_pths
60
aa56e112 61fun setUser user =
998ed174
AC
62 let
63 val () = usr := user
64
65 val your_paths = Acl.class {user = getUser (),
66 class = "path"}
67 in
04502362 68 fakePrivs := false;
998ed174
AC
69 your_doms := Acl.class {user = getUser (),
70 class = "domain"};
71 your_usrs := Acl.class {user = getUser (),
72 class = "user"};
73 your_grps := Acl.class {user = getUser (),
74 class = "group"};
75 your_pths := your_paths;
26c7d224
AC
76 readable_pths := SS.union (your_paths, world_readable);
77 your_ipss := Acl.class {user = getUser (),
78 class = "ip"}
998ed174 79 end
aa56e112 80
04502362
AC
81fun declareClient () = isClient := true
82fun fakePrivileges () = if !isClient then
83 fakePrivs := true
84 else
85 raise Fail "Tried to fake privileges as non-client"
86
6ae327f8
AC
87fun validIp s =
88 case map Int.fromString (String.fields (fn ch => ch = #".") s) of
89 [SOME n1, SOME n2, SOME n3, SOME n4] =>
90 n1 >= 0 andalso n1 < 256 andalso n2 >= 0 andalso n2 < 256 andalso n3 >= 0 andalso n3 < 256 andalso n4 >= 0 andalso n4 < 256
91 | _ => false
92
090692f7
AC
93fun isHexDigit ch = Char.isDigit ch orelse (ord ch >= ord #"a" andalso ord ch <= ord #"f")
94
95fun validIpv6 s =
96 let
97 val fields = String.fields (fn ch => ch = #":") s
98
99 val empties = foldl (fn ("", n) => n + 1
100 | (_, n) => n) 0 fields
101
102 fun noIpv4 maxLen =
103 length fields >= 2
104 andalso length fields <= maxLen
105 andalso empties <= 1
106 andalso List.all (fn "" => true
107 | s => size s <= 4
108 andalso CharVector.all isHexDigit s) fields
109
110 fun hasIpv4 () =
111 length fields > 0
112 andalso
113 let
114 val maybeIpv4 = List.last fields
115 val theRest = List.take (fields, length fields - 1)
116 in
117 validIp maybeIpv4 andalso noIpv4 6
118 end
119 in
120 noIpv4 8 orelse hasIpv4 ()
121 end
122
629a34f6
AC
123fun isIdent ch = Char.isLower ch orelse Char.isDigit ch
124
125fun validHost s =
16c5174b 126 size s > 0 andalso size s < 50
629a34f6
AC
127 andalso CharVector.all (fn ch => isIdent ch orelse ch = #"-") s
128
129fun validDomain s =
16c5174b 130 size s > 0 andalso size s < 200
629a34f6
AC
131 andalso List.all validHost (String.fields (fn ch => ch = #".") s)
132
2ed6d0e5 133fun validNode s = List.exists (fn s' => s = s') nodes
e0b0abd2 134
04502362 135fun yourDomain s = !fakePrivs orelse SS.member (your_domains (), s)
fdc43c6c
AC
136fun yourUser s = SS.member (your_users (), s)
137fun yourGroup s = SS.member (your_groups (), s)
998ed174 138fun checkPath paths path =
04502362
AC
139 (List.all (fn s => s <> "..") (String.fields (fn ch => ch = #"/") path)
140 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"/"
141 orelse ch = #"-" orelse ch = #"_") path
142 andalso SS.exists (fn s' => path = s' orelse String.isPrefix (s' ^ "/") path) (paths ()))
998ed174
AC
143val yourPath = checkPath your_paths
144val readablePath = checkPath readable_paths
04502362 145fun yourIp s = !fakePrivs orelse SS.member (your_ips (), s)
12adf55a 146
edd38024 147fun yourDomainHost s =
04502362
AC
148 !fakePrivs
149 orelse yourDomain s
c98b57cf 150 orelse let
edd38024
AC
151 val (pref, suf) = Substring.splitl (fn ch => ch <> #".") (Substring.full s)
152 in
edd38024
AC
153 Substring.size suf > 0
154 andalso validHost (Substring.string pref)
155 andalso yourDomain (Substring.string
c98b57cf 156 (Substring.slice (suf, 1, NONE)))
edd38024
AC
157 end
158
93c2f623
AC
159val yourDomain = yourDomainHost
160
2aeb9eec
AC
161fun validUser s = size s > 0 andalso size s < 20
162 andalso CharVector.all Char.isAlphaNum s
163
2e96b9d4
AC
164fun validEmailUser s =
165 size s > 0 andalso size s < 50
166 andalso CharVector.all (fn ch => Char.isAlphaNum ch
167 orelse ch = #"."
168 orelse ch = #"_"
169 orelse ch = #"-"
170 orelse ch = #"+") s
171
2aeb9eec
AC
172val validGroup = validUser
173
f8dfbbcc
AC
174val _ = Env.type_one "no_spaces"
175 Env.string
ca6ffb3f
AC
176 (CharVector.all (fn ch => Char.isPrint ch andalso not (Char.isSpace ch)
177 andalso ch <> #"\"" andalso ch <> #"'"))
d5754b53
AC
178val _ = Env.type_one "no_newlines"
179 Env.string
ca6ffb3f 180 (CharVector.all (fn ch => Char.isPrint ch andalso ch <> #"\n" andalso ch <> #"\r"
5e3ad5d2 181 andalso ch <> #"\""))
f8dfbbcc 182
6ae327f8
AC
183val _ = Env.type_one "ip"
184 Env.string
185 validIp
186
090692f7
AC
187val _ = Env.type_one "ipv6"
188 Env.string
189 validIpv6
190
629a34f6
AC
191val _ = Env.type_one "host"
192 Env.string
193 validHost
194
195val _ = Env.type_one "domain"
196 Env.string
197 validDomain
198
12adf55a
AC
199val _ = Env.type_one "your_domain"
200 Env.string
201 yourDomain
202
edd38024
AC
203val _ = Env.type_one "your_domain_host"
204 Env.string
205 yourDomainHost
206
2aeb9eec
AC
207val _ = Env.type_one "user"
208 Env.string
209 validUser
210
211val _ = Env.type_one "group"
212 Env.string
213 validGroup
214
8a7c40fa
AC
215val _ = Env.type_one "your_user"
216 Env.string
217 yourUser
218
219val _ = Env.type_one "your_group"
220 Env.string
221 yourGroup
222
223val _ = Env.type_one "your_path"
224 Env.string
225 yourPath
226
998ed174
AC
227val _ = Env.type_one "readable_path"
228 Env.string
229 readablePath
230
26c7d224
AC
231val _ = Env.type_one "your_ip"
232 Env.string
233 yourIp
234
e0b0abd2
AC
235val _ = Env.type_one "node"
236 Env.string
237 validNode
238
fb09779a
AC
239val _ = Env.type_one "mime_type"
240 Env.string
241 (CharVector.exists (fn ch => ch = #"/"))
242
2e87719c
AC
243val _ = Env.registerFunction ("your_ip_to_ip",
244 fn [e] => SOME e
245 | _ => NONE)
246
bbdf617f
AC
247val _ = Env.registerFunction ("dns_node_to_node",
248 fn [e] => SOME e
249 | _ => NONE)
250
251val _ = Env.registerFunction ("mail_node_to_node",
be1bea4c
AC
252 fn [e] => SOME e
253 | _ => NONE)
b0963032
AC
254
255
a3698041
AC
256open Ast
257
6ae327f8
AC
258val dl = ErrorMsg.dummyLoc
259
b0963032
AC
260val _ = Env.registerFunction ("end_in_slash",
261 fn [(EString "", _)] => SOME (EString "/", dl)
262 | [(EString s, _)] =>
263 SOME (EString (if String.sub (s, size s - 1) = #"/" then
264 s
265 else
266 s ^ "/"), dl)
267 | _ => NONE)
268
269
6ae327f8
AC
270val nsD = (EString Config.defaultNs, dl)
271val serialD = (EVar "serialAuto", dl)
272val refD = (EInt Config.defaultRefresh, dl)
273val retD = (EInt Config.defaultRetry, dl)
274val expD = (EInt Config.defaultExpiry, dl)
275val minD = (EInt Config.defaultMinimum, dl)
276
277val soaD = multiApp ((EVar "soa", dl),
278 dl,
279 [nsD, serialD, refD, retD, expD, minD])
280
e0b0abd2 281val masterD = (EApp ((EVar "internalMaster", dl),
8b84db5b 282 (EString Config.masterNode, dl)),
e0b0abd2
AC
283 dl)
284
8b84db5b
AC
285val slavesD = (EList (map (fn s => (EString s, dl)) Config.slaveNodes), dl)
286
e0b80e65
AC
287val _ = Defaults.registerDefault ("Aliases",
288 (TList (TBase "your_domain", dl), dl),
289 (fn () => (EList [], dl)))
290
6bb366c5
AC
291val _ = Defaults.registerDefault ("Mailbox",
292 (TBase "email", dl),
293 (fn () => (EString (getUser ()), dl)))
294
aa56e112
AC
295val _ = Defaults.registerDefault ("DNS",
296 (TBase "dnsKind", dl),
297 (fn () => multiApp ((EVar "useDns", dl),
298 dl,
8b84db5b 299 [soaD, masterD, slavesD])))
6ae327f8 300
aa56e112
AC
301val _ = Defaults.registerDefault ("TTL",
302 (TBase "int", dl),
303 (fn () => (EInt Config.Bind.defaultTTL, dl)))
6ae327f8
AC
304
305type soa = {ns : string,
306 serial : int option,
307 ref : int,
308 ret : int,
309 exp : int,
310 min : int}
311
312val serial = fn (EVar "serialAuto", _) => SOME NONE
313 | (EApp ((EVar "serialConst", _), n), _) => Option.map SOME (Env.int n)
314 | _ => NONE
315
316val soa = fn (EApp ((EApp ((EApp ((EApp ((EApp ((EApp
317 ((EVar "soa", _), ns), _),
318 sl), _),
319 rf), _),
320 ret), _),
321 exp), _),
322 min), _) =>
323 (case (Env.string ns, serial sl, Env.int rf,
324 Env.int ret, Env.int exp, Env.int min) of
325 (SOME ns, SOME sl, SOME rf,
326 SOME ret, SOME exp, SOME min) =>
327 SOME {ns = ns,
328 serial = sl,
329 ref = rf,
330 ret = ret,
331 exp = exp,
332 min = min}
333 | _ => NONE)
334 | _ => NONE
335
e0b0abd2
AC
336datatype master =
337 ExternalMaster of string
338 | InternalMaster of string
339
cf879b4f
AC
340val ip = Env.string
341
342val _ = Env.registerFunction ("ip_of_node",
343 fn [(EString node, _)] => SOME (EString (nodeIp node), dl)
344 | _ => NONE)
97665758
AC
345
346val master = fn (EApp ((EVar "externalMaster", _), e), _) => Option.map ExternalMaster (ip e)
8b84db5b 347 | (EApp ((EVar "internalMaster", _), e), _) => Option.map InternalMaster (Env.string e)
e0b0abd2
AC
348 | _ => NONE
349
6ae327f8 350datatype dnsKind =
e0b0abd2
AC
351 UseDns of {soa : soa,
352 master : master,
353 slaves : string list}
6ae327f8
AC
354 | NoDns
355
e0b0abd2
AC
356val dnsKind = fn (EApp ((EApp ((EApp
357 ((EVar "useDns", _), sa), _),
358 mstr), _),
359 slaves), _) =>
360 (case (soa sa, master mstr, Env.list Env.string slaves) of
361 (SOME sa, SOME mstr, SOME slaves) =>
362 SOME (UseDns {soa = sa,
363 master = mstr,
364 slaves = slaves})
365 | _ => NONE)
325285ab 366 | (EVar "noDns", _) => SOME NoDns
6ae327f8
AC
367 | _ => NONE
368
a3698041
AC
369val befores = ref (fn (_ : string) => ())
370val afters = ref (fn (_ : string) => ())
371
372fun registerBefore f =
373 let
374 val old = !befores
375 in
376 befores := (fn x => (old x; f x))
377 end
378
379fun registerAfter f =
380 let
381 val old = !afters
382 in
383 afters := (fn x => (old x; f x))
384 end
385
71420f8b
AC
386val globals = ref (fn () => ())
387val locals = ref (fn () => ())
388
389fun registerResetGlobal f =
390 let
391 val old = !globals
392 in
393 globals := (fn x => (old x; f x))
394 end
395
396fun registerResetLocal f =
397 let
398 val old = !locals
399 in
400 locals := (fn x => (old x; f x))
401 end
402
403fun resetGlobal () = (!globals ();
404 ignore (OS.Process.system (Config.rm ^ " -rf " ^ Config.resultRoot ^ "/*")))
405fun resetLocal () = !locals ()
406
a3698041 407val current = ref ""
e0b0abd2 408val currentPath = ref (fn (_ : string) => "")
e0b80e65 409val currentPathAli = ref (fn (_ : string, _ : string) => "")
dac62e84 410
d612d62c
AC
411val scratch = ref ""
412
dac62e84
AC
413fun currentDomain () = !current
414
e0b80e65
AC
415val currentsAli = ref ([] : string list)
416
417fun currentAliasDomains () = !currentsAli
418fun currentDomains () = currentDomain () :: currentAliasDomains ()
419
e0b0abd2
AC
420fun domainFile {node, name} = ((*print ("Opening " ^ !currentPath node ^ name ^ "\n");*)
421 TextIO.openOut (!currentPath node ^ name))
dac62e84 422
e0b80e65
AC
423type files = {write : string -> unit,
424 writeDom : unit -> unit,
425 close : unit -> unit}
426
427fun domainsFile {node, name} =
428 let
429 val doms = currentDomains ()
430 val files = map (fn dom => (dom, TextIO.openOut (!currentPathAli (dom, node) ^ name))) doms
431 in
432 {write = fn s => app (fn (_, outf) => TextIO.output (outf, s)) files,
433 writeDom = fn () => app (fn (dom, outf) => TextIO.output (outf, dom)) files,
434 close = fn () => app (fn (_, outf) => TextIO.closeOut outf) files}
435 end
436
dac62e84
AC
437fun getPath domain =
438 let
439 val toks = String.fields (fn ch => ch = #".") domain
440
441 val elems = foldr (fn (piece, elems) =>
442 let
443 val elems = piece :: elems
d612d62c 444
e0b0abd2
AC
445 fun doNode node =
446 let
447 val path = String.concatWith "/"
448 (Config.resultRoot :: node :: rev elems)
449 val tmpPath = String.concatWith "/"
450 (Config.tmpDir :: node :: rev elems)
451 in
452 (if Posix.FileSys.ST.isDir
453 (Posix.FileSys.stat path) then
454 ()
455 else
456 (OS.FileSys.remove path;
457 OS.FileSys.mkDir path))
458 handle OS.SysErr _ => OS.FileSys.mkDir path;
459
460 (if Posix.FileSys.ST.isDir
461 (Posix.FileSys.stat tmpPath) then
462 ()
463 else
464 (OS.FileSys.remove tmpPath;
465 OS.FileSys.mkDir tmpPath))
466 handle OS.SysErr _ => OS.FileSys.mkDir tmpPath
467 end
468 in
2ed6d0e5 469 app doNode nodes;
dac62e84
AC
470 elems
471 end) [] toks
472 in
e0b0abd2 473 fn (root, site) => String.concatWith "/" (root :: site :: rev ("" :: elems))
d612d62c
AC
474 end
475
476datatype file_action' =
477 Add' of {src : string, dst : string}
478 | Delete' of string
479 | Modify' of {src : string, dst : string}
480
aaf70d45 481fun findDiffs (prefixes, site, dom, acts) =
d612d62c 482 let
e0b0abd2
AC
483 val gp = getPath dom
484 val realPath = gp (Config.resultRoot, site)
485 val tmpPath = gp (Config.tmpDir, site)
486
487 (*val _ = print ("getDiffs(" ^ site ^ ", " ^ dom ^ ")... " ^ realPath ^ "; " ^ tmpPath ^ "\n")*)
d612d62c
AC
488
489 val dir = Posix.FileSys.opendir realPath
490
491 fun loopReal acts =
492 case Posix.FileSys.readdir dir of
493 NONE => (Posix.FileSys.closedir dir;
494 acts)
495 | SOME fname =>
496 let
497 val real = OS.Path.joinDirFile {dir = realPath,
498 file = fname}
499 val tmp = OS.Path.joinDirFile {dir = tmpPath,
500 file = fname}
501 in
502 if Posix.FileSys.ST.isDir (Posix.FileSys.stat real) then
503 loopReal acts
504 else if Posix.FileSys.access (tmp, []) then
8df2e702 505 if Slave.shell [Config.diff, " ", real, " ", tmp] then
d612d62c
AC
506 loopReal acts
507 else
e0b0abd2 508 loopReal ((site, dom, realPath, Modify' {src = tmp, dst = real}) :: acts)
4f9c5b51 509 else if List.exists (fn prefix => String.isPrefix prefix real) prefixes then
e0b0abd2 510 loopReal ((site, dom, realPath, Delete' real) :: acts)
aaf70d45
AC
511 else
512 loopReal acts
d612d62c
AC
513 end
514
e0b0abd2 515 val acts = loopReal acts
d612d62c 516
8df2e702 517 val dir = Posix.FileSys.opendir tmpPath
d612d62c
AC
518
519 fun loopTmp acts =
520 case Posix.FileSys.readdir dir of
521 NONE => (Posix.FileSys.closedir dir;
522 acts)
523 | SOME fname =>
524 let
525 val real = OS.Path.joinDirFile {dir = realPath,
526 file = fname}
527 val tmp = OS.Path.joinDirFile {dir = tmpPath,
528 file = fname}
529 in
530 if Posix.FileSys.ST.isDir (Posix.FileSys.stat tmp) then
531 loopTmp acts
532 else if Posix.FileSys.access (real, []) then
533 loopTmp acts
534 else
e0b0abd2 535 loopTmp ((site, dom, realPath, Add' {src = tmp, dst = real}) :: acts)
d612d62c
AC
536 end
537
538 val acts = loopTmp acts
539 in
540 acts
dac62e84 541 end
a3698041 542
aaf70d45 543fun findAllDiffs prefixes =
e0b0abd2
AC
544 let
545 val dir = Posix.FileSys.opendir Config.tmpDir
546 val len = length (String.fields (fn ch => ch = #"/") Config.tmpDir) + 1
547
548 fun exploreSites diffs =
549 case Posix.FileSys.readdir dir of
550 NONE => diffs
551 | SOME site =>
552 let
553 fun explore (dname, diffs) =
554 let
555 val dir = Posix.FileSys.opendir dname
556
557 fun loop diffs =
558 case Posix.FileSys.readdir dir of
559 NONE => diffs
560 | SOME name =>
561 let
562 val fname = OS.Path.joinDirFile {dir = dname,
563 file = name}
564 in
565 loop (if Posix.FileSys.ST.isDir (Posix.FileSys.stat fname) then
566 let
567 val dom = String.fields (fn ch => ch = #"/") fname
568 val dom = List.drop (dom, len)
569 val dom = String.concatWith "." (rev dom)
570
571 val dname' = OS.Path.joinDirFile {dir = dname,
572 file = name}
573 in
574 explore (dname',
aaf70d45 575 findDiffs (prefixes, site, dom, diffs))
e0b0abd2
AC
576 end
577 else
578 diffs)
579 end
580 in
581 loop diffs
582 before Posix.FileSys.closedir dir
583 end
584 in
36e42cb8
AC
585 exploreSites (explore (OS.Path.joinDirFile {dir = Config.tmpDir,
586 file = site}, diffs))
e0b0abd2
AC
587 end
588 in
589 exploreSites []
590 before Posix.FileSys.closedir dir
591 end
592
593val masterNode : string option ref = ref NONE
594fun dnsMaster () = !masterNode
595
aaf70d45
AC
596val seenDomains : string list ref = ref []
597
6ae327f8
AC
598val _ = Env.containerV_one "domain"
599 ("domain", Env.string)
600 (fn (evs, dom) =>
601 let
aaf70d45
AC
602 val () = seenDomains := dom :: !seenDomains
603
6ae327f8
AC
604 val kind = Env.env dnsKind (evs, "DNS")
605 val ttl = Env.env Env.int (evs, "TTL")
e0b80e65 606 val aliases = Env.env (Env.list Env.string) (evs, "Aliases")
6ae327f8 607
e0b0abd2 608 val path = getPath dom
6ae327f8
AC
609
610 val () = (current := dom;
e0b80e65
AC
611 currentsAli := Slave.remove (Slave.removeDups aliases, dom);
612 currentPath := (fn site => path (Config.tmpDir, site));
613 currentPathAli := (fn (dom, site) => getPath dom (Config.tmpDir, site)))
6ae327f8 614
e0b0abd2 615 fun saveSoa (kind, soa : soa) node =
e2359100 616 let
3ae703b6 617 val {write, writeDom, close} = domainsFile {node = node, name = "soa.conf"}
e2359100 618 in
e0b80e65
AC
619 write kind;
620 write "\n";
621 write (Int.toString ttl);
622 write "\n";
623 write (#ns soa);
624 write "\n";
e2359100
AC
625 case #serial soa of
626 NONE => ()
e0b80e65
AC
627 | SOME n => write (Int.toString n);
628 write "\n";
629 write (Int.toString (#ref soa));
630 write "\n";
631 write (Int.toString (#ret soa));
632 write "\n";
633 write (Int.toString (#exp soa));
634 write "\n";
635 write (Int.toString (#min soa));
636 write "\n";
637 close ()
e2359100 638 end
6ae327f8 639
a431ca34 640 fun saveNamed (kind, soa : soa, masterIp, slaveIps) node =
b341fd6d
AC
641 if dom = "localhost" then
642 ()
643 else let
e0b80e65 644 val {write, writeDom, close} = domainsFile {node = node, name = "named.conf"}
b341fd6d 645 in
e0b80e65
AC
646 write "\nzone \"";
647 writeDom ();
a431ca34 648 write "\" {\n\ttype ";
e0b80e65
AC
649 write kind;
650 write ";\n\tfile \"";
651 write Config.Bind.zonePath_real;
652 write "/";
653 writeDom ();
654 write ".zone\";\n";
b341fd6d 655 case kind of
a431ca34
AC
656 "master" => (write "\tallow-transfer {\n";
657 app (fn ip => (write "\t\t";
658 write ip;
659 write ";\n")) slaveIps;
660 write "\t};\n")
e0b80e65
AC
661 | _ => (write "\tmasters { ";
662 write masterIp;
036cfc59
AC
663 write "; };\n";
664 write "// Updated: ";
665 write (Time.toString (Time.now ()));
666 write "\n");
e0b80e65
AC
667 write "};\n";
668 close ()
b341fd6d 669 end
6ae327f8
AC
670 in
671 case kind of
e0b0abd2
AC
672 NoDns => masterNode := NONE
673 | UseDns dns =>
2ed6d0e5
AC
674 let
675 val masterIp =
676 case #master dns of
a431ca34 677 InternalMaster node => nodeIp node
2ed6d0e5 678 | ExternalMaster ip => ip
a431ca34
AC
679
680 val slaveIps = map nodeIp (#slaves dns)
2ed6d0e5
AC
681 in
682 app (saveSoa ("slave", #soa dns)) (#slaves dns);
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");
714 if site = Config.defaultNode then
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
c189cbe9
AC
816 fun doNode (node, _) =
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
AC
864
865 fun cleanupNode (node, _) =
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 ())
894
314ce7bd
AC
895type subject = {node : string, domain : string}
896
897val describers : (subject -> string) list ref = ref []
898
899fun registerDescriber f = describers := f :: !describers
900
41c58daf 901fun describeOne arg = String.concat (map (fn f => f arg) (rev (!describers)))
314ce7bd 902
d300166d
AC
903val line = "--------------------------------------------------------------\n"
904val dline = "==============================================================\n"
314ce7bd
AC
905
906fun describe dom =
907 String.concat (List.mapPartial
908 (fn node =>
909 case describeOne {node = node, domain = dom} of
910 "" => NONE
911 | s =>
912 SOME (String.concat [dline, "Node ", node, "\n", dline, "\n", s]))
913 nodes)
914
915datatype description =
41c58daf 916 Filename of { filename : string, heading : string, showEmpty : bool }
314ce7bd
AC
917 | Extension of { extension : string, heading : string -> string }
918
919fun considerAll ds {node, domain} =
920 let
921 val ds = map (fn d => (d, ref [])) ds
922
923 val path = Config.resultRoot
924 val jdf = OS.Path.joinDirFile
925 val path = jdf {dir = path, file = node}
926 val path = foldr (fn (more, path) => jdf {dir = path, file = more})
927 path (String.tokens (fn ch => ch = #".") domain)
928 in
929 if Posix.FileSys.access (path, []) then
930 let
931 val dir = Posix.FileSys.opendir path
932
933 fun loop () =
934 case Posix.FileSys.readdir dir of
935 NONE => ()
936 | SOME fname =>
41c58daf
AC
937 (app (fn (d, entries) =>
938 let
939 fun readFile showEmpty entries' =
940 let
941 val fname = OS.Path.joinDirFile {dir = path,
942 file = fname}
943
944 val inf = TextIO.openIn fname
945
946 fun loop (seenOne, entries') =
947 case TextIO.inputLine inf of
948 NONE => if seenOne orelse showEmpty then
949 "\n" :: entries'
950 else
951 !entries
952 | SOME line => loop (true, line :: entries')
953 in
954 loop (false, entries')
955 before TextIO.closeIn inf
956 end
957 in
958 case d of
959 Filename {filename, heading, showEmpty} =>
960 if fname = filename then
d936cf4d 961 entries := readFile showEmpty ("\n" :: line :: "\n" :: heading :: line :: !entries)
41c58daf
AC
962 else
963 ()
964 | Extension {extension, heading} =>
965 let
966 val {base, ext} = OS.Path.splitBaseExt fname
967 in
968 case ext of
969 NONE => ()
970 | SOME extension' =>
971 if extension' = extension then
d936cf4d 972 entries := readFile true ("\n" :: line :: "\n" :: heading base :: line :: !entries)
41c58daf
AC
973 else
974 ()
975 end
976 end) ds;
977 loop ())
314ce7bd
AC
978 in
979 loop ();
980 Posix.FileSys.closedir dir;
981 String.concat (List.concat (map (fn (_, entries) => rev (!entries)) ds))
982 end
983 else
984 ""
985 end
986
3ae703b6 987val () = registerDescriber (considerAll [Filename {filename = "soa.conf",
d936cf4d 988 heading = "DNS SOA:",
41c58daf 989 showEmpty = false}])
314ce7bd 990
e9f528ab
AC
991val () = Env.registerAction ("domainHost",
992 fn (env, [(EString host, _)]) =>
993 SM.insert (env, "Hostname",
994 (EString (host ^ "." ^ currentDomain ()), dl))
995 | (_, args) => Env.badArgs ("domainHost", args))
996
563e7792
AC
997val ouc = ref (fn () => ())
998
999fun registerOnUsersChange f =
1000 let
1001 val f' = !ouc
1002 in
1003 ouc := (fn () => (f' (); f ()))
1004 end
1005
1006fun onUsersChange () = !ouc ()
1007
a3698041 1008end