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