Domain alias support for e-mail aliases
[hcoop/domtool2.git] / src / domain.sml
CommitLineData
a3698041
AC
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dac62e84 17 *)
a3698041
AC
18
19(* Domain-related primitive actions *)
20
21structure Domain :> DOMAIN = struct
22
36e42cb8
AC
23open MsgTypes
24
2ed6d0e5 25structure SM = DataStructures.StringMap
12adf55a 26structure SS = DataStructures.StringSet
2ed6d0e5 27
36e42cb8
AC
28val ssl_context = ref (NONE : OpenSSL.context option)
29fun set_context ctx = ssl_context := SOME ctx
30
12adf55a 31val nodes = map #1 Config.nodeIps
2ed6d0e5
AC
32val nodeMap = foldl (fn ((node, ip), mp) => SM.insert (mp, node, ip))
33 SM.empty Config.nodeIps
8a7c40fa 34fun nodeIp node = valOf (SM.find (nodeMap, node))
2ed6d0e5 35
12adf55a 36val usr = ref ""
12adf55a
AC
37fun getUser () = !usr
38
39val your_doms = ref SS.empty
40fun your_domains () = !your_doms
41
8a7c40fa
AC
42val your_usrs = ref SS.empty
43fun your_users () = !your_usrs
44
45val your_grps = ref SS.empty
46fun your_groups () = !your_grps
47
48val your_pths = ref SS.empty
49fun your_paths () = !your_pths
50
aa56e112 51fun setUser user =
a088cea6 52 (usr := user;
aa56e112
AC
53 your_doms := Acl.class {user = getUser (),
54 class = "domain"};
55 your_usrs := Acl.class {user = getUser (),
56 class = "user"};
57 your_grps := Acl.class {user = getUser (),
58 class = "group"};
59 your_pths := Acl.class {user = getUser (),
60 class = "path"})
61
6ae327f8
AC
62fun validIp s =
63 case map Int.fromString (String.fields (fn ch => ch = #".") s) of
64 [SOME n1, SOME n2, SOME n3, SOME n4] =>
65 n1 >= 0 andalso n1 < 256 andalso n2 >= 0 andalso n2 < 256 andalso n3 >= 0 andalso n3 < 256 andalso n4 >= 0 andalso n4 < 256
66 | _ => false
67
629a34f6
AC
68fun isIdent ch = Char.isLower ch orelse Char.isDigit ch
69
70fun validHost s =
71 size s > 0 andalso size s < 20
72 andalso CharVector.all (fn ch => isIdent ch orelse ch = #"-") s
73
74fun validDomain s =
75 size s > 0 andalso size s < 100
76 andalso List.all validHost (String.fields (fn ch => ch = #".") s)
77
2ed6d0e5 78fun validNode s = List.exists (fn s' => s = s') nodes
e0b0abd2 79
12adf55a 80fun yourDomain s = SS.member (your_domains (), s)
8a7c40fa
AC
81fun yourUser s = SS.member (your_users (), s)
82fun yourGroup s = SS.member (your_groups (), s)
83fun yourPath path =
84 List.all (fn s => s <> "..") (String.fields (fn ch => ch = #"/") path)
85 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"/"
86 orelse ch = #"-" orelse ch = #"_") path
87 andalso SS.exists (fn s' => path = s' orelse String.isPrefix (s' ^ "/") path) (your_paths ())
12adf55a 88
edd38024 89fun yourDomainHost s =
c98b57cf
AC
90 yourDomain s
91 orelse let
edd38024
AC
92 val (pref, suf) = Substring.splitl (fn ch => ch <> #".") (Substring.full s)
93 in
edd38024
AC
94 Substring.size suf > 0
95 andalso validHost (Substring.string pref)
96 andalso yourDomain (Substring.string
c98b57cf 97 (Substring.slice (suf, 1, NONE)))
edd38024
AC
98 end
99
93c2f623
AC
100val yourDomain = yourDomainHost
101
2aeb9eec
AC
102fun validUser s = size s > 0 andalso size s < 20
103 andalso CharVector.all Char.isAlphaNum s
104
2e96b9d4
AC
105fun validEmailUser s =
106 size s > 0 andalso size s < 50
107 andalso CharVector.all (fn ch => Char.isAlphaNum ch
108 orelse ch = #"."
109 orelse ch = #"_"
110 orelse ch = #"-"
111 orelse ch = #"+") s
112
2aeb9eec
AC
113val validGroup = validUser
114
f8dfbbcc
AC
115val _ = Env.type_one "no_spaces"
116 Env.string
117 (CharVector.all (fn ch => not (Char.isSpace ch)))
d5754b53
AC
118val _ = Env.type_one "no_newlines"
119 Env.string
120 (CharVector.all (fn ch => ch <> #"\n" andalso ch <> #"\r"))
f8dfbbcc 121
6ae327f8
AC
122val _ = Env.type_one "ip"
123 Env.string
124 validIp
125
629a34f6
AC
126val _ = Env.type_one "host"
127 Env.string
128 validHost
129
130val _ = Env.type_one "domain"
131 Env.string
132 validDomain
133
12adf55a
AC
134val _ = Env.type_one "your_domain"
135 Env.string
136 yourDomain
137
edd38024
AC
138val _ = Env.type_one "your_domain_host"
139 Env.string
140 yourDomainHost
141
2aeb9eec
AC
142val _ = Env.type_one "user"
143 Env.string
144 validUser
145
146val _ = Env.type_one "group"
147 Env.string
148 validGroup
149
8a7c40fa
AC
150val _ = Env.type_one "your_user"
151 Env.string
152 yourUser
153
154val _ = Env.type_one "your_group"
155 Env.string
156 yourGroup
157
158val _ = Env.type_one "your_path"
159 Env.string
160 yourPath
161
e0b0abd2
AC
162val _ = Env.type_one "node"
163 Env.string
164 validNode
165
bbdf617f
AC
166val _ = Env.registerFunction ("dns_node_to_node",
167 fn [e] => SOME e
168 | _ => NONE)
169
170val _ = Env.registerFunction ("mail_node_to_node",
be1bea4c
AC
171 fn [e] => SOME e
172 | _ => NONE)
a3698041
AC
173open Ast
174
6ae327f8
AC
175val dl = ErrorMsg.dummyLoc
176
177val nsD = (EString Config.defaultNs, dl)
178val serialD = (EVar "serialAuto", dl)
179val refD = (EInt Config.defaultRefresh, dl)
180val retD = (EInt Config.defaultRetry, dl)
181val expD = (EInt Config.defaultExpiry, dl)
182val minD = (EInt Config.defaultMinimum, dl)
183
184val soaD = multiApp ((EVar "soa", dl),
185 dl,
186 [nsD, serialD, refD, retD, expD, minD])
187
e0b0abd2 188val masterD = (EApp ((EVar "internalMaster", dl),
8b84db5b 189 (EString Config.masterNode, dl)),
e0b0abd2
AC
190 dl)
191
8b84db5b
AC
192val slavesD = (EList (map (fn s => (EString s, dl)) Config.slaveNodes), dl)
193
e0b80e65
AC
194val _ = Defaults.registerDefault ("Aliases",
195 (TList (TBase "your_domain", dl), dl),
196 (fn () => (EList [], dl)))
197
6bb366c5
AC
198val _ = Defaults.registerDefault ("Mailbox",
199 (TBase "email", dl),
200 (fn () => (EString (getUser ()), dl)))
201
aa56e112
AC
202val _ = Defaults.registerDefault ("DNS",
203 (TBase "dnsKind", dl),
204 (fn () => multiApp ((EVar "useDns", dl),
205 dl,
8b84db5b 206 [soaD, masterD, slavesD])))
6ae327f8 207
aa56e112
AC
208val _ = Defaults.registerDefault ("TTL",
209 (TBase "int", dl),
210 (fn () => (EInt Config.Bind.defaultTTL, dl)))
6ae327f8
AC
211
212type soa = {ns : string,
213 serial : int option,
214 ref : int,
215 ret : int,
216 exp : int,
217 min : int}
218
219val serial = fn (EVar "serialAuto", _) => SOME NONE
220 | (EApp ((EVar "serialConst", _), n), _) => Option.map SOME (Env.int n)
221 | _ => NONE
222
223val soa = fn (EApp ((EApp ((EApp ((EApp ((EApp ((EApp
224 ((EVar "soa", _), ns), _),
225 sl), _),
226 rf), _),
227 ret), _),
228 exp), _),
229 min), _) =>
230 (case (Env.string ns, serial sl, Env.int rf,
231 Env.int ret, Env.int exp, Env.int min) of
232 (SOME ns, SOME sl, SOME rf,
233 SOME ret, SOME exp, SOME min) =>
234 SOME {ns = ns,
235 serial = sl,
236 ref = rf,
237 ret = ret,
238 exp = exp,
239 min = min}
240 | _ => NONE)
241 | _ => NONE
242
e0b0abd2
AC
243datatype master =
244 ExternalMaster of string
245 | InternalMaster of string
246
cf879b4f
AC
247val ip = Env.string
248
249val _ = Env.registerFunction ("ip_of_node",
250 fn [(EString node, _)] => SOME (EString (nodeIp node), dl)
251 | _ => NONE)
97665758
AC
252
253val master = fn (EApp ((EVar "externalMaster", _), e), _) => Option.map ExternalMaster (ip e)
8b84db5b 254 | (EApp ((EVar "internalMaster", _), e), _) => Option.map InternalMaster (Env.string e)
e0b0abd2
AC
255 | _ => NONE
256
6ae327f8 257datatype dnsKind =
e0b0abd2
AC
258 UseDns of {soa : soa,
259 master : master,
260 slaves : string list}
6ae327f8
AC
261 | NoDns
262
e0b0abd2
AC
263val dnsKind = fn (EApp ((EApp ((EApp
264 ((EVar "useDns", _), sa), _),
265 mstr), _),
266 slaves), _) =>
267 (case (soa sa, master mstr, Env.list Env.string slaves) of
268 (SOME sa, SOME mstr, SOME slaves) =>
269 SOME (UseDns {soa = sa,
270 master = mstr,
271 slaves = slaves})
272 | _ => NONE)
325285ab 273 | (EVar "noDns", _) => SOME NoDns
6ae327f8
AC
274 | _ => NONE
275
a3698041
AC
276val befores = ref (fn (_ : string) => ())
277val afters = ref (fn (_ : string) => ())
278
279fun registerBefore f =
280 let
281 val old = !befores
282 in
283 befores := (fn x => (old x; f x))
284 end
285
286fun registerAfter f =
287 let
288 val old = !afters
289 in
290 afters := (fn x => (old x; f x))
291 end
292
71420f8b
AC
293val globals = ref (fn () => ())
294val locals = ref (fn () => ())
295
296fun registerResetGlobal f =
297 let
298 val old = !globals
299 in
300 globals := (fn x => (old x; f x))
301 end
302
303fun registerResetLocal f =
304 let
305 val old = !locals
306 in
307 locals := (fn x => (old x; f x))
308 end
309
310fun resetGlobal () = (!globals ();
311 ignore (OS.Process.system (Config.rm ^ " -rf " ^ Config.resultRoot ^ "/*")))
312fun resetLocal () = !locals ()
313
a3698041 314val current = ref ""
e0b0abd2 315val currentPath = ref (fn (_ : string) => "")
e0b80e65 316val currentPathAli = ref (fn (_ : string, _ : string) => "")
dac62e84 317
d612d62c
AC
318val scratch = ref ""
319
dac62e84
AC
320fun currentDomain () = !current
321
e0b80e65
AC
322val currentsAli = ref ([] : string list)
323
324fun currentAliasDomains () = !currentsAli
325fun currentDomains () = currentDomain () :: currentAliasDomains ()
326
e0b0abd2
AC
327fun domainFile {node, name} = ((*print ("Opening " ^ !currentPath node ^ name ^ "\n");*)
328 TextIO.openOut (!currentPath node ^ name))
dac62e84 329
e0b80e65
AC
330type files = {write : string -> unit,
331 writeDom : unit -> unit,
332 close : unit -> unit}
333
334fun domainsFile {node, name} =
335 let
336 val doms = currentDomains ()
337 val files = map (fn dom => (dom, TextIO.openOut (!currentPathAli (dom, node) ^ name))) doms
338 in
339 {write = fn s => app (fn (_, outf) => TextIO.output (outf, s)) files,
340 writeDom = fn () => app (fn (dom, outf) => TextIO.output (outf, dom)) files,
341 close = fn () => app (fn (_, outf) => TextIO.closeOut outf) files}
342 end
343
dac62e84
AC
344fun getPath domain =
345 let
346 val toks = String.fields (fn ch => ch = #".") domain
347
348 val elems = foldr (fn (piece, elems) =>
349 let
350 val elems = piece :: elems
d612d62c 351
e0b0abd2
AC
352 fun doNode node =
353 let
354 val path = String.concatWith "/"
355 (Config.resultRoot :: node :: rev elems)
356 val tmpPath = String.concatWith "/"
357 (Config.tmpDir :: node :: rev elems)
358 in
359 (if Posix.FileSys.ST.isDir
360 (Posix.FileSys.stat path) then
361 ()
362 else
363 (OS.FileSys.remove path;
364 OS.FileSys.mkDir path))
365 handle OS.SysErr _ => OS.FileSys.mkDir path;
366
367 (if Posix.FileSys.ST.isDir
368 (Posix.FileSys.stat tmpPath) then
369 ()
370 else
371 (OS.FileSys.remove tmpPath;
372 OS.FileSys.mkDir tmpPath))
373 handle OS.SysErr _ => OS.FileSys.mkDir tmpPath
374 end
375 in
2ed6d0e5 376 app doNode nodes;
dac62e84
AC
377 elems
378 end) [] toks
379 in
e0b0abd2 380 fn (root, site) => String.concatWith "/" (root :: site :: rev ("" :: elems))
d612d62c
AC
381 end
382
383datatype file_action' =
384 Add' of {src : string, dst : string}
385 | Delete' of string
386 | Modify' of {src : string, dst : string}
387
e0b0abd2 388fun findDiffs (site, dom, acts) =
d612d62c 389 let
e0b0abd2
AC
390 val gp = getPath dom
391 val realPath = gp (Config.resultRoot, site)
392 val tmpPath = gp (Config.tmpDir, site)
393
394 (*val _ = print ("getDiffs(" ^ site ^ ", " ^ dom ^ ")... " ^ realPath ^ "; " ^ tmpPath ^ "\n")*)
d612d62c
AC
395
396 val dir = Posix.FileSys.opendir realPath
397
398 fun loopReal acts =
399 case Posix.FileSys.readdir dir of
400 NONE => (Posix.FileSys.closedir dir;
401 acts)
402 | SOME fname =>
403 let
404 val real = OS.Path.joinDirFile {dir = realPath,
405 file = fname}
406 val tmp = OS.Path.joinDirFile {dir = tmpPath,
407 file = fname}
408 in
409 if Posix.FileSys.ST.isDir (Posix.FileSys.stat real) then
410 loopReal acts
411 else if Posix.FileSys.access (tmp, []) then
8df2e702 412 if Slave.shell [Config.diff, " ", real, " ", tmp] then
d612d62c
AC
413 loopReal acts
414 else
e0b0abd2 415 loopReal ((site, dom, realPath, Modify' {src = tmp, dst = real}) :: acts)
d612d62c 416 else
e0b0abd2 417 loopReal ((site, dom, realPath, Delete' real) :: acts)
d612d62c
AC
418 end
419
e0b0abd2 420 val acts = loopReal acts
d612d62c 421
8df2e702 422 val dir = Posix.FileSys.opendir tmpPath
d612d62c
AC
423
424 fun loopTmp acts =
425 case Posix.FileSys.readdir dir of
426 NONE => (Posix.FileSys.closedir dir;
427 acts)
428 | SOME fname =>
429 let
430 val real = OS.Path.joinDirFile {dir = realPath,
431 file = fname}
432 val tmp = OS.Path.joinDirFile {dir = tmpPath,
433 file = fname}
434 in
435 if Posix.FileSys.ST.isDir (Posix.FileSys.stat tmp) then
436 loopTmp acts
437 else if Posix.FileSys.access (real, []) then
438 loopTmp acts
439 else
e0b0abd2 440 loopTmp ((site, dom, realPath, Add' {src = tmp, dst = real}) :: acts)
d612d62c
AC
441 end
442
443 val acts = loopTmp acts
444 in
445 acts
dac62e84 446 end
a3698041 447
e0b0abd2
AC
448fun findAllDiffs () =
449 let
450 val dir = Posix.FileSys.opendir Config.tmpDir
451 val len = length (String.fields (fn ch => ch = #"/") Config.tmpDir) + 1
452
453 fun exploreSites diffs =
454 case Posix.FileSys.readdir dir of
455 NONE => diffs
456 | SOME site =>
457 let
458 fun explore (dname, diffs) =
459 let
460 val dir = Posix.FileSys.opendir dname
461
462 fun loop diffs =
463 case Posix.FileSys.readdir dir of
464 NONE => diffs
465 | SOME name =>
466 let
467 val fname = OS.Path.joinDirFile {dir = dname,
468 file = name}
469 in
470 loop (if Posix.FileSys.ST.isDir (Posix.FileSys.stat fname) then
471 let
472 val dom = String.fields (fn ch => ch = #"/") fname
473 val dom = List.drop (dom, len)
474 val dom = String.concatWith "." (rev dom)
475
476 val dname' = OS.Path.joinDirFile {dir = dname,
477 file = name}
478 in
479 explore (dname',
480 findDiffs (site, dom, diffs))
481 end
482 else
483 diffs)
484 end
485 in
486 loop diffs
487 before Posix.FileSys.closedir dir
488 end
489 in
36e42cb8
AC
490 exploreSites (explore (OS.Path.joinDirFile {dir = Config.tmpDir,
491 file = site}, diffs))
e0b0abd2
AC
492 end
493 in
494 exploreSites []
495 before Posix.FileSys.closedir dir
496 end
497
498val masterNode : string option ref = ref NONE
499fun dnsMaster () = !masterNode
500
6ae327f8
AC
501val _ = Env.containerV_one "domain"
502 ("domain", Env.string)
503 (fn (evs, dom) =>
504 let
505 val kind = Env.env dnsKind (evs, "DNS")
506 val ttl = Env.env Env.int (evs, "TTL")
e0b80e65 507 val aliases = Env.env (Env.list Env.string) (evs, "Aliases")
6ae327f8 508
e0b0abd2 509 val path = getPath dom
6ae327f8
AC
510
511 val () = (current := dom;
e0b80e65
AC
512 currentsAli := Slave.remove (Slave.removeDups aliases, dom);
513 currentPath := (fn site => path (Config.tmpDir, site));
514 currentPathAli := (fn (dom, site) => getPath dom (Config.tmpDir, site)))
6ae327f8 515
e0b0abd2 516 fun saveSoa (kind, soa : soa) node =
e2359100 517 let
e0b80e65 518 val {write, writeDom, close} = domainsFile {node = node, name = "soa"}
e2359100 519 in
e0b80e65
AC
520 write kind;
521 write "\n";
522 write (Int.toString ttl);
523 write "\n";
524 write (#ns soa);
525 write "\n";
e2359100
AC
526 case #serial soa of
527 NONE => ()
e0b80e65
AC
528 | SOME n => write (Int.toString n);
529 write "\n";
530 write (Int.toString (#ref soa));
531 write "\n";
532 write (Int.toString (#ret soa));
533 write "\n";
534 write (Int.toString (#exp soa));
535 write "\n";
536 write (Int.toString (#min soa));
537 write "\n";
538 close ()
e2359100 539 end
6ae327f8 540
2ed6d0e5 541 fun saveNamed (kind, soa : soa, masterIp) node =
b341fd6d
AC
542 if dom = "localhost" then
543 ()
544 else let
e0b80e65 545 val {write, writeDom, close} = domainsFile {node = node, name = "named.conf"}
b341fd6d 546 in
e0b80e65
AC
547 write "\nzone \"";
548 writeDom ();
549 write "\" IN {\n\ttype ";
550 write kind;
551 write ";\n\tfile \"";
552 write Config.Bind.zonePath_real;
553 write "/";
554 writeDom ();
555 write ".zone\";\n";
b341fd6d 556 case kind of
e0b80e65
AC
557 "master" => write "\tallow-update { none; };\n"
558 | _ => (write "\tmasters { ";
559 write masterIp;
560 write "; };\n");
561 write "};\n";
562 close ()
b341fd6d 563 end
6ae327f8
AC
564 in
565 case kind of
e0b0abd2
AC
566 NoDns => masterNode := NONE
567 | UseDns dns =>
2ed6d0e5
AC
568 let
569 val masterIp =
570 case #master dns of
571 InternalMaster node => valOf (SM.find (nodeMap, node))
572 | ExternalMaster ip => ip
573 in
574 app (saveSoa ("slave", #soa dns)) (#slaves dns);
575 app (saveNamed ("slave", #soa dns, masterIp)) (#slaves dns);
576 case #master dns of
577 InternalMaster node =>
578 (masterNode := SOME node;
579 saveSoa ("master", #soa dns) node;
580 saveNamed ("master", #soa dns, masterIp) node)
581 | _ => masterNode := NONE;
582 !befores dom
583 end
6ae327f8 584 end,
e0b0abd2
AC
585 fn () => !afters (!current))
586
587val () = Env.registerPre (fn () => (ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
588 fn cl => "Temp file cleanup failed: " ^ cl));
589 OS.FileSys.mkDir Config.tmpDir;
590 app (fn node => OS.FileSys.mkDir
591 (OS.Path.joinDirFile {dir = Config.tmpDir,
592 file = node}))
2ed6d0e5 593 nodes;
e0b0abd2
AC
594 app (fn node => OS.FileSys.mkDir
595 (OS.Path.joinDirFile {dir = Config.resultRoot,
596 file = node})
597 handle OS.SysErr _ => ())
2ed6d0e5 598 nodes))
e0b0abd2 599
c189cbe9
AC
600fun handleSite (site, files) =
601 let
602
603 in
604 print ("New configuration for node " ^ site ^ "\n");
605 if site = Config.defaultNode then
606 Slave.handleChanges files
607 else let
608 val bio = OpenSSL.connect (valOf (!ssl_context),
609 nodeIp site
610 ^ ":"
611 ^ Int.toString Config.slavePort)
612 in
613 app (fn file => Msg.send (bio, MsgFile file)) files;
614 Msg.send (bio, MsgDoFiles);
615 case Msg.recv bio of
616 NONE => print "Slave closed connection unexpectedly\n"
617 | SOME m =>
618 case m of
619 MsgOk => print ("Slave " ^ site ^ " finished\n")
620 | MsgError s => print ("Slave " ^ site
621 ^ " returned error: " ^
622 s ^ "\n")
623 | _ => print ("Slave " ^ site
624 ^ " returned unexpected command\n");
625 OpenSSL.close bio
626 end
627 end
628
e0b0abd2
AC
629val () = Env.registerPost (fn () =>
630 let
631 val diffs = findAllDiffs ()
6ae327f8 632
e0b0abd2
AC
633 val diffs = map (fn (site, dom, dir, Add' {src, dst}) =>
634 (Slave.shellF ([Config.cp, " ", src, " ", dst],
635 fn cl => "Copy failed: " ^ cl);
636 (site,
6ae327f8
AC
637 {action = Slave.Add,
638 domain = dom,
639 dir = dir,
e0b0abd2
AC
640 file = dst}))
641 | (site, dom, dir, Delete' dst) =>
642 (OS.FileSys.remove dst
643 handle OS.SysErr _ =>
644 ErrorMsg.error NONE ("Delete failed for " ^ dst);
645 (site,
6ae327f8
AC
646 {action = Slave.Delete,
647 domain = dom,
648 dir = dir,
e0b0abd2
AC
649 file = dst}))
650 | (site, dom, dir, Modify' {src, dst}) =>
651 (Slave.shellF ([Config.cp, " ", src, " ", dst],
652 fn cl => "Copy failed: " ^ cl);
653 (site,
6ae327f8
AC
654 {action = Slave.Modify,
655 domain = dom,
656 dir = dir,
e0b0abd2
AC
657 file = dst}))) diffs
658 in
659 if !ErrorMsg.anyErrors then
660 ()
36e42cb8
AC
661 else let
662 val changed = foldl (fn ((site, file), changed) =>
663 let
664 val ls = case SM.find (changed, site) of
665 NONE => []
666 | SOME ls => ls
667 in
668 SM.insert (changed, site, file :: ls)
669 end) SM.empty diffs
36e42cb8
AC
670 in
671 SM.appi handleSite changed
672 end;
e0b0abd2 673 ignore (Slave.shellF ([Config.rm, " -rf ", Config.tmpDir, ""],
559e89e9 674 fn cl => "Temp file cleanup failed: " ^ cl))
e0b0abd2 675 end)
6ae327f8 676
be1bea4c
AC
677fun hasPriv priv = Acl.query {user = getUser (), class = "priv", value = "all"}
678 orelse Acl.query {user = getUser (), class = "priv", value = priv}
679
680val _ = Env.type_one "dns_node"
681 Env.string
682 (fn node =>
683 List.exists (fn x => x = node) Config.dnsNodes_all
684 orelse (hasPriv "dns"
685 andalso List.exists (fn x => x = node) Config.dnsNodes_admin))
60695e99 686
bbdf617f
AC
687val _ = Env.type_one "mail_node"
688 Env.string
689 (fn node =>
690 List.exists (fn x => x = node) Config.mailNodes_all
691 orelse (hasPriv "mail"
692 andalso List.exists (fn x => x = node) Config.mailNodes_admin))
693
e69e60cc 694fun rmdom doms =
c189cbe9 695 let
c189cbe9
AC
696 fun doNode (node, _) =
697 let
698 val dname = OS.Path.joinDirFile {dir = Config.resultRoot,
699 file = node}
c189cbe9 700
e69e60cc 701 fun doDom (dom, actions) =
93c2f623 702 let
e69e60cc
AC
703 val domPath = String.concatWith "/" (rev (String.fields (fn ch => ch = #".") dom))
704 val dname = OS.Path.concat (dname, domPath)
705
706 fun visitDom (dom, dname, actions) =
707 let
708 val dir = Posix.FileSys.opendir dname
709
710 fun loop actions =
711 case Posix.FileSys.readdir dir of
712 NONE => actions
713 | SOME fname =>
714 let
715 val fnameFull = OS.Path.joinDirFile {dir = dname,
716 file = fname}
717 in
718 if Posix.FileSys.ST.isDir (Posix.FileSys.stat fnameFull) then
719 loop (visitDom (fname ^ "." ^ dom,
720 fnameFull,
721 actions))
722 else
723 loop ({action = Slave.Delete,
724 domain = dom,
725 dir = dname,
726 file = fnameFull} :: actions)
727 end
728 in
729 loop actions
730 before Posix.FileSys.closedir dir
731 end
732 handle OS.SysErr _ =>
f208fe7e 733 (print ("Warning: System error deleting domain " ^ dom ^ " on " ^ node ^ ".\n");
e69e60cc 734 actions)
93c2f623 735 in
e69e60cc 736 visitDom (dom, dname, actions)
93c2f623
AC
737 end
738
e69e60cc 739 val actions = foldl doDom [] doms
c189cbe9 740 in
c189cbe9
AC
741 handleSite (node, actions)
742 end
e69e60cc 743 handle IO.Io _ => print ("Warning: IO error deleting domains on " ^ node ^ ".\n")
c189cbe9
AC
744
745 fun cleanupNode (node, _) =
746 let
e69e60cc
AC
747 fun doDom dom =
748 let
749 val domPath = String.concatWith "/" (rev (String.fields (fn ch => ch = #".") dom))
750 val dname = OS.Path.joinDirFile {dir = Config.resultRoot,
751 file = node}
752 val dname = OS.Path.concat (dname, domPath)
753 in
754 ignore (OS.Process.system (Config.rm ^ " -rf " ^ dname))
755 end
c189cbe9 756 in
e69e60cc 757 app doDom doms
c189cbe9
AC
758 end
759 in
760 app doNode Config.nodeIps;
761 app cleanupNode Config.nodeIps
762 end
763
0da1c677
AC
764fun homedirOf uname =
765 Posix.SysDB.Passwd.home (Posix.SysDB.getpwnam uname)
766
767fun homedir () = homedirOf (getUser ())
768
a3698041 769end