mysql: revoke permissions when dropping database
[hcoop/domtool2.git] / src / main.sml
CommitLineData
234b917a 1(* HCoop Domtool (http://hcoop.sourceforge.net/)
563e7792 2 * Copyright (c) 2006-2009, Adam Chlipala
617696c6 3 * Copyright (c) 2012,2013,2014 Clinton Ebadi <clinton@unknownlamer.org>
234b917a
AC
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dac62e84 18 *)
234b917a
AC
19
20(* Main interface *)
21
22structure Main :> MAIN = struct
23
36e42cb8 24open Ast MsgTypes Print
234b917a 25
6ae327f8
AC
26structure SM = StringMap
27
aa56e112 28fun init () = Acl.read Config.aclFile
24248d62
AC
29
30fun isLib fname = OS.Path.file fname = "lib.dtl"
31
32fun wrapFile (fname, file) =
33 case (isLib fname, file) of
34 (true, (comment, ds, SOME e)) =>
35 let
36 val (_, loc) = e
37 in
38 (comment, ds, SOME (ELocal (e, (ESkip, loc)), loc))
39 end
40 | _ => file
41
d189ec0e 42fun check' G fname =
a3698041
AC
43 let
44 val prog = Parse.parse fname
24248d62 45 val prog = wrapFile (fname, prog)
a3698041
AC
46 in
47 if !ErrorMsg.anyErrors then
d189ec0e 48 G
a3698041 49 else
e9256fdc
AC
50 (if isLib fname then
51 ()
52 else
53 Option.app (Unused.check G) (#3 prog);
e140629f 54 Tycheck.checkFile G prog)
a3698041
AC
55 end
56
d189ec0e 57fun basis () =
234b917a 58 let
5e7777d4 59 val _ = ErrorMsg.reset ()
d189ec0e
AC
60 val dir = Posix.FileSys.opendir Config.libRoot
61
62 fun loop files =
63 case Posix.FileSys.readdir dir of
d612d62c
AC
64 NONE => (Posix.FileSys.closedir dir;
65 files)
d189ec0e
AC
66 | SOME fname =>
67 if String.isSuffix ".dtl" fname then
d612d62c
AC
68 loop (OS.Path.joinDirFile {dir = Config.libRoot,
69 file = fname}
d189ec0e
AC
70 :: files)
71 else
72 loop files
73
74 val files = loop []
c53e82e4 75 val (_, files) = Order.order NONE files
d189ec0e 76 in
6ae327f8
AC
77 if !ErrorMsg.anyErrors then
78 Env.empty
79 else
b3159a70
AC
80 (Tycheck.allowExterns ();
81 foldl (fn (fname, G) => check' G fname) Env.empty files
82 before Tycheck.disallowExterns ())
d189ec0e
AC
83 end
84
24248d62
AC
85(* val b = basis () *)
86
87fun check G fname =
d189ec0e
AC
88 let
89 val _ = ErrorMsg.reset ()
12adf55a 90 val _ = Env.preTycheck ()
234b917a
AC
91 in
92 if !ErrorMsg.anyErrors then
36e42cb8 93 raise ErrorMsg.Error
234b917a
AC
94 else
95 let
b3159a70 96 val _ = Tycheck.disallowExterns ()
7f012ffd 97 val _ = ErrorMsg.reset ()
d189ec0e 98 val prog = Parse.parse fname
24248d62 99 val prog = wrapFile (fname, prog)
234b917a 100 in
492c1cff 101 if !ErrorMsg.anyErrors then
36e42cb8 102 raise ErrorMsg.Error
492c1cff 103 else
d189ec0e 104 let
e140629f 105 val G' = Tycheck.checkFile G prog
d189ec0e 106 in
36e42cb8
AC
107 if !ErrorMsg.anyErrors then
108 raise ErrorMsg.Error
109 else
e9256fdc
AC
110 (if isLib fname then
111 ()
112 else
113 Option.app (Unused.check G) (#3 prog);
b21491de 114 (G', #3 prog))
d189ec0e 115 end
234b917a
AC
116 end
117 end
118
0c85f25e
AC
119fun notTmp s =
120 String.sub (s, 0) <> #"."
9df38662 121 andalso s <> "_darcs"
0c85f25e 122 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"_" orelse ch = #"-") s
c53e82e4 123
86e132be
AC
124fun setupUser () =
125 let
126 val user =
127 case Posix.ProcEnv.getenv "DOMTOOL_USER" of
128 NONE =>
129 let
130 val uid = Posix.ProcEnv.getuid ()
131 in
132 Posix.SysDB.Passwd.name (Posix.SysDB.getpwuid uid)
133 end
134 | SOME user => user
135 in
136 Acl.read Config.aclFile;
137 Domain.setUser user;
138 user
139 end
140
141fun checkDir' dname =
c53e82e4
AC
142 let
143 val b = basis ()
144
145 val dir = Posix.FileSys.opendir dname
146
147 fun loop files =
148 case Posix.FileSys.readdir dir of
149 NONE => (Posix.FileSys.closedir dir;
150 files)
151 | SOME fname =>
152 if notTmp fname then
153 loop (OS.Path.joinDirFile {dir = dname,
154 file = fname}
155 :: files)
156 else
157 loop files
158
159 val files = loop []
160 val (_, files) = Order.order (SOME b) files
161 in
162 if !ErrorMsg.anyErrors then
1824f573 163 raise ErrorMsg.Error
c53e82e4
AC
164 else
165 (foldl (fn (fname, G) => check' G fname) b files;
1824f573
AC
166 if !ErrorMsg.anyErrors then
167 raise ErrorMsg.Error
168 else
169 ())
c53e82e4
AC
170 end
171
86e132be
AC
172fun checkDir dname =
173 (setupUser ();
174 checkDir' dname)
175
24248d62 176fun reduce G fname =
a3698041 177 let
24248d62 178 val (G, body) = check G fname
a3698041
AC
179 in
180 if !ErrorMsg.anyErrors then
06bd8215 181 (G, NONE)
a3698041 182 else
d189ec0e
AC
183 case body of
184 SOME body =>
185 let
186 val body' = Reduce.reduceExp G body
187 in
188 (*printd (PD.hovBox (PD.PPS.Rel 0,
189 [PD.string "Result:",
190 PD.space 1,
191 p_exp body']))*)
06bd8215 192 (G, SOME body')
d189ec0e 193 end
06bd8215 194 | _ => (G, NONE)
a3698041
AC
195 end
196
24248d62 197(*(Defaults.eInit ())*)
d189ec0e 198
254d5faa
CE
199val toplevel = Env.initialDynEnvVals Reduce.reduceExp
200
24248d62
AC
201fun eval G evs fname =
202 case reduce G fname of
06bd8215 203 (G, SOME body') =>
1824f573
AC
204 if !ErrorMsg.anyErrors then
205 raise ErrorMsg.Error
206 else
24248d62 207 let
254d5faa 208 val evs' = Eval.exec' (toplevel G, evs) body'
24248d62
AC
209 in
210 (G, evs')
211 end
06bd8215 212 | (G, NONE) => (G, evs)
1824f573 213
3b267643 214val dispatcher =
201b83c7 215 Domain.nodeIp Config.dispatcherName ^ ":" ^ Int.toString Config.dispatcherPort
559e89e9 216
c9731b9b
AC
217val self =
218 "localhost:" ^ Int.toString Config.slavePort
219
d22c1f00
AC
220fun context x =
221 (OpenSSL.context false x)
25c93232 222 handle e as OpenSSL.OpenSSL s =>
d22c1f00 223 (print "Couldn't find your certificate.\nYou probably haven't been given any Domtool privileges.\n";
504618b9 224 print ("I looked in: " ^ #1 x ^ "\n");
25c93232 225 print ("Additional information: " ^ s ^ "\n");
d22c1f00
AC
226 raise e)
227
e1b99e23
AC
228fun requestContext f =
229 let
230 val user = setupUser ()
5ee41dd0
AC
231
232 val () = f ()
aa56e112 233
d22c1f00
AC
234 val context = context (Config.certDir ^ "/" ^ user ^ ".pem",
235 Config.keyDir ^ "/" ^ user ^ "/key.pem",
236 Config.trustStore)
5ee41dd0
AC
237 in
238 (user, context)
239 end
07cc384c 240
8be753d9 241fun requestBio' printErr f =
5ee41dd0
AC
242 let
243 val (user, context) = requestContext f
244 in
8be753d9 245 (user, OpenSSL.connect printErr (context, dispatcher))
5ee41dd0
AC
246 end
247
8be753d9
AC
248val requestBio = requestBio' true
249
250fun requestSlaveBio' printErr =
c9731b9b
AC
251 let
252 val (user, context) = requestContext (fn () => ())
253 in
8be753d9 254 (user, OpenSSL.connect printErr (context, self))
c9731b9b
AC
255 end
256
8be753d9
AC
257fun requestSlaveBio () = requestSlaveBio' true
258
9a34b001 259fun request (fname, libOpt) =
5ee41dd0 260 let
9a34b001
AC
261 val (user, bio) = requestBio (fn () =>
262 let
263 val env = basis ()
264 val env = case libOpt of
265 NONE => env
266 | SOME lib => #1 (check env lib)
267 in
268 ignore (check env fname)
269 end)
270
271 fun readFile fname =
272 let
273 val inf = TextIO.openIn fname
3b267643 274
9a34b001
AC
275 fun loop lines =
276 case TextIO.inputLine inf of
277 NONE => String.concat (rev lines)
278 | SOME line => loop (line :: lines)
279 in
280 loop []
281 before TextIO.closeIn inf
282 end
36e42cb8 283
9a34b001
AC
284 val code = readFile fname
285 val msg = case libOpt of
286 NONE => MsgConfig code
287 | SOME fname' => MsgMultiConfig [readFile fname', code]
559e89e9 288 in
9a34b001 289 Msg.send (bio, msg);
36e42cb8
AC
290 case Msg.recv bio of
291 NONE => print "Server closed connection unexpectedly.\n"
292 | SOME m =>
293 case m of
294 MsgOk => print "Configuration succeeded.\n"
295 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
296 | _ => print "Unexpected server reply.\n";
3b267643 297 OpenSSL.close bio
559e89e9 298 end
aa56e112 299 handle ErrorMsg.Error => ()
559e89e9 300
c53e82e4
AC
301fun requestDir dname =
302 let
5982c377
AC
303 val _ = if Posix.FileSys.access (dname, []) then
304 ()
305 else
306 (print ("Can't access " ^ dname ^ ".\n");
307 print "Did you mean to run domtool on a specific file, instead of asking for all\n";
e7905534 308 print "files in your ~/.domtool directory?\n";
5982c377
AC
309 OS.Process.exit OS.Process.failure)
310
1824f573
AC
311 val _ = ErrorMsg.reset ()
312
86e132be 313 val (user, bio) = requestBio (fn () => checkDir' dname)
c53e82e4
AC
314
315 val b = basis ()
316
317 val dir = Posix.FileSys.opendir dname
318
319 fun loop files =
320 case Posix.FileSys.readdir dir of
321 NONE => (Posix.FileSys.closedir dir;
322 files)
323 | SOME fname =>
324 if notTmp fname then
325 loop (OS.Path.joinDirFile {dir = dname,
326 file = fname}
327 :: files)
328 else
329 loop files
330
331 val files = loop []
332 val (_, files) = Order.order (SOME b) files
333
334 val _ = if !ErrorMsg.anyErrors then
563e7792 335 raise ErrorMsg.Error
c53e82e4
AC
336 else
337 ()
338
339 val codes = map (fn fname =>
340 let
341 val inf = TextIO.openIn fname
342
343 fun loop lines =
344 case TextIO.inputLine inf of
345 NONE => String.concat (rev lines)
346 | SOME line => loop (line :: lines)
347 in
348 loop []
349 before TextIO.closeIn inf
350 end) files
351 in
1824f573
AC
352 if !ErrorMsg.anyErrors then
353 ()
354 else
355 (Msg.send (bio, MsgMultiConfig codes);
356 case Msg.recv bio of
357 NONE => print "Server closed connection unexpectedly.\n"
358 | SOME m =>
359 case m of
360 MsgOk => print "Configuration succeeded.\n"
361 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
362 | _ => print "Unexpected server reply.\n";
363 OpenSSL.close bio)
c53e82e4
AC
364 end
365 handle ErrorMsg.Error => ()
366
62260c5f
AC
367fun requestPing () =
368 let
8be753d9 369 val (_, bio) = requestBio' false (fn () => ())
62260c5f
AC
370 in
371 OpenSSL.close bio;
372 OS.Process.success
373 end
374 handle _ => OS.Process.failure
375
9f27d58f
AC
376fun requestShutdown () =
377 let
378 val (_, bio) = requestBio (fn () => ())
379 in
380 Msg.send (bio, MsgShutdown);
381 case Msg.recv bio of
8be753d9 382 NONE => ()
9f27d58f
AC
383 | SOME m =>
384 case m of
385 MsgOk => print "Shutdown begun.\n"
386 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
387 | _ => print "Unexpected server reply.\n";
388 OpenSSL.close bio
389 end
390
c9731b9b
AC
391fun requestSlavePing () =
392 let
8be753d9 393 val (_, bio) = requestSlaveBio' false
c9731b9b
AC
394 in
395 OpenSSL.close bio;
396 OS.Process.success
397 end
398 handle _ => OS.Process.failure
399
400fun requestSlaveShutdown () =
401 let
402 val (_, bio) = requestSlaveBio ()
403 in
404 Msg.send (bio, MsgShutdown);
405 case Msg.recv bio of
8be753d9 406 NONE => ()
c9731b9b
AC
407 | SOME m =>
408 case m of
409 MsgOk => print "Shutdown begun.\n"
410 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
411 | _ => print "Unexpected server reply.\n";
412 OpenSSL.close bio
413 end
414
5ee41dd0
AC
415fun requestGrant acl =
416 let
417 val (user, bio) = requestBio (fn () => ())
418 in
419 Msg.send (bio, MsgGrant acl);
420 case Msg.recv bio of
421 NONE => print "Server closed connection unexpectedly.\n"
422 | SOME m =>
423 case m of
424 MsgOk => print "Grant succeeded.\n"
425 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
426 | _ => print "Unexpected server reply.\n";
427 OpenSSL.close bio
428 end
429
411a85f2
AC
430fun requestRevoke acl =
431 let
432 val (user, bio) = requestBio (fn () => ())
433 in
434 Msg.send (bio, MsgRevoke acl);
435 case Msg.recv bio of
436 NONE => print "Server closed connection unexpectedly.\n"
437 | SOME m =>
438 case m of
439 MsgOk => print "Revoke succeeded.\n"
440 | MsgError s => print ("Revoke failed: " ^ s ^ "\n")
441 | _ => print "Unexpected server reply.\n";
442 OpenSSL.close bio
443 end
444
08a04eb4
AC
445fun requestListPerms user =
446 let
447 val (_, bio) = requestBio (fn () => ())
448 in
449 Msg.send (bio, MsgListPerms user);
450 (case Msg.recv bio of
451 NONE => (print "Server closed connection unexpectedly.\n";
452 NONE)
453 | SOME m =>
454 case m of
455 MsgPerms perms => SOME perms
456 | MsgError s => (print ("Listing failed: " ^ s ^ "\n");
457 NONE)
458 | _ => (print "Unexpected server reply.\n";
459 NONE))
460 before OpenSSL.close bio
461 end
462
094877b1
AC
463fun requestWhoHas perm =
464 let
465 val (_, bio) = requestBio (fn () => ())
466 in
467 Msg.send (bio, MsgWhoHas perm);
468 (case Msg.recv bio of
469 NONE => (print "Server closed connection unexpectedly.\n";
470 NONE)
471 | SOME m =>
472 case m of
473 MsgWhoHasResponse users => SOME users
474 | MsgError s => (print ("whohas failed: " ^ s ^ "\n");
475 NONE)
476 | _ => (print "Unexpected server reply.\n";
477 NONE))
478 before OpenSSL.close bio
479 end
480
1824f573
AC
481fun requestRegen () =
482 let
483 val (_, bio) = requestBio (fn () => ())
484 in
485 Msg.send (bio, MsgRegenerate);
486 case Msg.recv bio of
487 NONE => print "Server closed connection unexpectedly.\n"
488 | SOME m =>
489 case m of
490 MsgOk => print "Regeneration succeeded.\n"
491 | MsgError s => print ("Regeneration failed: " ^ s ^ "\n")
492 | _ => print "Unexpected server reply.\n";
493 OpenSSL.close bio
494 end
495
fb6fac97
AC
496fun requestRegenTc () =
497 let
498 val (_, bio) = requestBio (fn () => ())
499 in
500 Msg.send (bio, MsgRegenerateTc);
501 case Msg.recv bio of
502 NONE => print "Server closed connection unexpectedly.\n"
503 | SOME m =>
504 case m of
505 MsgOk => print "All configuration validated.\n"
506 | MsgError s => print ("Configuration validation failed: " ^ s ^ "\n")
507 | _ => print "Unexpected server reply.\n";
508 OpenSSL.close bio
509 end
510
c189cbe9
AC
511fun requestRmdom dom =
512 let
513 val (_, bio) = requestBio (fn () => ())
514 in
515 Msg.send (bio, MsgRmdom dom);
516 case Msg.recv bio of
517 NONE => print "Server closed connection unexpectedly.\n"
518 | SOME m =>
519 case m of
520 MsgOk => print "Removal succeeded.\n"
521 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
522 | _ => print "Unexpected server reply.\n";
523 OpenSSL.close bio
524 end
525
e69e60cc
AC
526fun requestRmuser user =
527 let
528 val (_, bio) = requestBio (fn () => ())
529 in
530 Msg.send (bio, MsgRmuser user);
531 case Msg.recv bio of
532 NONE => print "Server closed connection unexpectedly.\n"
533 | SOME m =>
534 case m of
535 MsgOk => print "Removal succeeded.\n"
536 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
537 | _ => print "Unexpected server reply.\n";
538 OpenSSL.close bio
539 end
540
d541c618
AC
541fun requestDbUser dbtype =
542 let
c362e4cc
CE
543 val (_, context) = requestContext (fn () => ())
544 val bio = OpenSSL.connect true (context,
4555c311 545 Domain.nodeIp Config.Dbms.dbmsNode ^ ":" ^ Int.toString Config.slavePort)
d541c618
AC
546 in
547 Msg.send (bio, MsgCreateDbUser dbtype);
548 case Msg.recv bio of
549 NONE => print "Server closed connection unexpectedly.\n"
550 | SOME m =>
551 case m of
552 MsgOk => print "Your user has been created.\n"
553 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
554 | _ => print "Unexpected server reply.\n";
555 OpenSSL.close bio
556 end
557
86aa5de7
AC
558fun requestDbPasswd rc =
559 let
c362e4cc
CE
560 val (_, context) = requestContext (fn () => ())
561 val bio = OpenSSL.connect true (context,
562 Config.Dbms.dbmsNode ^ ":" ^ Int.toString Config.slavePort)
86aa5de7
AC
563 in
564 Msg.send (bio, MsgDbPasswd rc);
565 case Msg.recv bio of
566 NONE => print "Server closed connection unexpectedly.\n"
567 | SOME m =>
568 case m of
569 MsgOk => print "Your password has been changed.\n"
570 | MsgError s => print ("Password set failed: " ^ s ^ "\n")
571 | _ => print "Unexpected server reply.\n";
572 OpenSSL.close bio
573 end
574
90dd48df
AC
575fun requestDbTable p =
576 let
c362e4cc
CE
577 val (user, context) = requestContext (fn () => ())
578 val bio = OpenSSL.connect true (context,
579 Config.Dbms.dbmsNode ^ ":" ^ Int.toString Config.slavePort)
90dd48df 580 in
7adeee33 581 Msg.send (bio, MsgCreateDb p);
90dd48df
AC
582 case Msg.recv bio of
583 NONE => print "Server closed connection unexpectedly.\n"
584 | SOME m =>
585 case m of
586 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been created.\n")
587 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
588 | _ => print "Unexpected server reply.\n";
589 OpenSSL.close bio
590 end
591
35659203
AC
592fun requestDbDrop p =
593 let
c362e4cc
CE
594 val (user, context) = requestContext (fn () => ())
595 val bio = OpenSSL.connect true (context,
596 Config.Dbms.dbmsNode ^ ":" ^ Int.toString Config.slavePort)
35659203
AC
597 in
598 Msg.send (bio, MsgDropDb p);
599 case Msg.recv bio of
600 NONE => print "Server closed connection unexpectedly.\n"
601 | SOME m =>
602 case m of
603 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been dropped.\n")
604 | MsgError s => print ("Drop failed: " ^ s ^ "\n")
605 | _ => print "Unexpected server reply.\n";
606 OpenSSL.close bio
607 end
608
99cc4144
AC
609fun requestDbGrant p =
610 let
c362e4cc
CE
611 val (user, context) = requestContext (fn () => ())
612 val bio = OpenSSL.connect true (context,
613 Config.Dbms.dbmsNode ^ ":" ^ Int.toString Config.slavePort)
99cc4144
AC
614 in
615 Msg.send (bio, MsgGrantDb p);
616 case Msg.recv bio of
617 NONE => print "Server closed connection unexpectedly.\n"
618 | SOME m =>
619 case m of
620 MsgOk => print ("You've been granted all allowed privileges to database " ^ user ^ "_" ^ #dbname p ^ ".\n")
621 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
622 | _ => print "Unexpected server reply.\n";
623 OpenSSL.close bio
624 end
625
1d3ef80e
AC
626fun requestListMailboxes domain =
627 let
628 val (_, bio) = requestBio (fn () => ())
629 in
630 Msg.send (bio, MsgListMailboxes domain);
631 (case Msg.recv bio of
2e96b9d4 632 NONE => Vmail.Error "Server closed connection unexpectedly."
1d3ef80e
AC
633 | SOME m =>
634 case m of
635 MsgMailboxes users => (Msg.send (bio, MsgOk);
636 Vmail.Listing users)
dee1a22b 637 | MsgError s => Vmail.Error ("Listing failed: " ^ s)
2e96b9d4 638 | _ => Vmail.Error "Unexpected server reply.")
1d3ef80e
AC
639 before OpenSSL.close bio
640 end
641
08688401
AC
642fun requestNewMailbox p =
643 let
644 val (_, bio) = requestBio (fn () => ())
645 in
646 Msg.send (bio, MsgNewMailbox p);
647 case Msg.recv bio of
648 NONE => print "Server closed connection unexpectedly.\n"
649 | SOME m =>
650 case m of
651 MsgOk => print ("A mapping for " ^ #user p ^ "@" ^ #domain p ^ " has been created.\n")
652 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
653 | _ => print "Unexpected server reply.\n";
654 OpenSSL.close bio
655 end
656
657fun requestPasswdMailbox p =
658 let
659 val (_, bio) = requestBio (fn () => ())
660 in
661 Msg.send (bio, MsgPasswdMailbox p);
662 case Msg.recv bio of
663 NONE => print "Server closed connection unexpectedly.\n"
664 | SOME m =>
665 case m of
666 MsgOk => print ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n")
667 | MsgError s => print ("Set failed: " ^ s ^ "\n")
668 | _ => print "Unexpected server reply.\n";
669 OpenSSL.close bio
670 end
671
0e0442b0
CE
672fun requestPortalPasswdMailbox p =
673 let
674 val (_, bio) = requestBio (fn () => ())
675 in
53043cda
CE
676 (Msg.send (bio, MsgPortalPasswdMailbox p);
677 case Msg.recv bio of
678 NONE => (print "Server closed connection unexpectedly.\n"; OS.Process.failure)
679 | SOME m =>
680 case m of
681 MsgOk => (print ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n");
682 OS.Process.success)
683 | MsgError s => (print ("Set failed: " ^ s ^ "\n"); OS.Process.failure)
684 | _ => (print "Unexpected server reply.\n"; OS.Process.failure))
685 before OpenSSL.close bio
686 end
0e0442b0 687
08688401
AC
688fun requestRmMailbox p =
689 let
690 val (_, bio) = requestBio (fn () => ())
691 in
692 Msg.send (bio, MsgRmMailbox p);
693 case Msg.recv bio of
694 NONE => print "Server closed connection unexpectedly.\n"
695 | SOME m =>
696 case m of
697 MsgOk => print ("The mapping for mailbox " ^ #user p ^ "@" ^ #domain p ^ " has been deleted.\n")
698 | MsgError s => print ("Remove failed: " ^ s ^ "\n")
699 | _ => print "Unexpected server reply.\n";
700 OpenSSL.close bio
701 end
702
2e96b9d4
AC
703fun requestSaQuery addr =
704 let
705 val (_, bio) = requestBio (fn () => ())
706 in
707 Msg.send (bio, MsgSaQuery addr);
708 (case Msg.recv bio of
709 NONE => print "Server closed connection unexpectedly.\n"
710 | SOME m =>
711 case m of
712 MsgSaStatus b => (print ("SpamAssassin filtering for " ^ addr ^ " is "
713 ^ (if b then "ON" else "OFF") ^ ".\n");
714 Msg.send (bio, MsgOk))
715 | MsgError s => print ("Query failed: " ^ s ^ "\n")
716 | _ => print "Unexpected server reply.\n")
717 before OpenSSL.close bio
718 end
719
720fun requestSaSet p =
721 let
722 val (_, bio) = requestBio (fn () => ())
723 in
724 Msg.send (bio, MsgSaSet p);
725 case Msg.recv bio of
726 NONE => print "Server closed connection unexpectedly.\n"
727 | SOME m =>
728 case m of
729 MsgOk => print ("SpamAssassin filtering for " ^ #1 p ^ " is now "
730 ^ (if #2 p then "ON" else "OFF") ^ ".\n")
731 | MsgError s => print ("Set failed: " ^ s ^ "\n")
732 | _ => print "Unexpected server reply.\n";
733 OpenSSL.close bio
734 end
735
2bc5ed22
AC
736fun requestSmtpLog domain =
737 let
738 val (_, bio) = requestBio (fn () => ())
739
740 val _ = Msg.send (bio, MsgSmtpLogReq domain)
741
742 fun loop () =
743 case Msg.recv bio of
744 NONE => print "Server closed connection unexpectedly.\n"
745 | SOME m =>
746 case m of
747 MsgOk => ()
748 | MsgSmtpLogRes line => (print line;
749 loop ())
750 | MsgError s => print ("Log search failed: " ^ s ^ "\n")
751 | _ => print "Unexpected server reply.\n"
752 in
753 loop ();
754 OpenSSL.close bio
755 end
756
75585a67
AC
757fun requestApt {node, pkg} =
758 let
a95a0107 759 val (user, context) = requestContext (fn () => ())
94b55bf7 760 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
761 dispatcher
762 else
763 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
75585a67 764
a95a0107 765 val _ = Msg.send (bio, MsgQuery (QApt pkg))
75585a67
AC
766
767 fun loop () =
768 case Msg.recv bio of
769 NONE => (print "Server closed connection unexpectedly.\n";
770 OS.Process.failure)
771 | SOME m =>
772 case m of
773 MsgYes => (print "Package is installed.\n";
774 OS.Process.success)
775 | MsgNo => (print "Package is not installed.\n";
776 OS.Process.failure)
777 | MsgError s => (print ("APT query failed: " ^ s ^ "\n");
778 OS.Process.failure)
779 | _ => (print "Unexpected server reply.\n";
780 OS.Process.failure)
781 in
782 loop ()
783 before OpenSSL.close bio
784 end
785
991d8e66
CE
786fun requestAptExists {node, pkg} =
787 let
788 val (user, context) = requestContext (fn () => ())
789 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
790 dispatcher
791 else
792 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
793
794 val _ = Msg.send (bio, MsgQuery (QAptExists pkg))
795
796 fun loop () =
797 case Msg.recv bio of
798 NONE => (print "Server closed connection unexpectedly.\n";
799 OS.Process.failure)
800 | SOME m =>
801 case m of
f296c496
CE
802 MsgAptQuery {section,description} => (print "Package exists.\n";
803 print ("Section: " ^ section ^ "\n");
804 print ("Description: " ^ description ^ "\n");
805 OS.Process.success)
991d8e66 806 | MsgNo => (print "Package does not exist.\n";
f296c496
CE
807 OS.Process.failure
808 (* It might be the Wrong Thing (tm) to use MsgNo like this *))
991d8e66
CE
809 | MsgError s => (print ("APT existence query failed: " ^ s ^ "\n");
810 OS.Process.failure)
811 | _ => (print "Unexpected server reply.\n";
812 OS.Process.failure)
813 in
814 loop ()
815 before OpenSSL.close bio
816 end
817
d351d679
AC
818fun requestCron {node, uname} =
819 let
820 val (user, context) = requestContext (fn () => ())
94b55bf7 821 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
822 dispatcher
823 else
824 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
d351d679
AC
825
826 val _ = Msg.send (bio, MsgQuery (QCron uname))
827
828 fun loop () =
829 case Msg.recv bio of
830 NONE => (print "Server closed connection unexpectedly.\n";
831 OS.Process.failure)
832 | SOME m =>
833 case m of
834 MsgYes => (print "User has cron permissions.\n";
835 OS.Process.success)
836 | MsgNo => (print "User does not have cron permissions.\n";
837 OS.Process.failure)
838 | MsgError s => (print ("Cron query failed: " ^ s ^ "\n");
839 OS.Process.failure)
840 | _ => (print "Unexpected server reply.\n";
841 OS.Process.failure)
842 in
843 loop ()
844 before OpenSSL.close bio
845 end
846
847fun requestFtp {node, uname} =
848 let
849 val (user, context) = requestContext (fn () => ())
94b55bf7 850 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
851 dispatcher
852 else
853 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
d351d679
AC
854
855 val _ = Msg.send (bio, MsgQuery (QFtp uname))
856
857 fun loop () =
858 case Msg.recv bio of
859 NONE => (print "Server closed connection unexpectedly.\n";
860 OS.Process.failure)
861 | SOME m =>
862 case m of
863 MsgYes => (print "User has FTP permissions.\n";
864 OS.Process.success)
865 | MsgNo => (print "User does not have FTP permissions.\n";
866 OS.Process.failure)
867 | MsgError s => (print ("FTP query failed: " ^ s ^ "\n");
868 OS.Process.failure)
869 | _ => (print "Unexpected server reply.\n";
870 OS.Process.failure)
871 in
872 loop ()
873 before OpenSSL.close bio
874 end
875
4d5126e1
AC
876fun requestTrustedPath {node, uname} =
877 let
878 val (user, context) = requestContext (fn () => ())
94b55bf7 879 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
880 dispatcher
881 else
882 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
4d5126e1
AC
883
884 val _ = Msg.send (bio, MsgQuery (QTrustedPath uname))
885
886 fun loop () =
887 case Msg.recv bio of
888 NONE => (print "Server closed connection unexpectedly.\n";
889 OS.Process.failure)
890 | SOME m =>
891 case m of
892 MsgYes => (print "User has trusted path restriction.\n";
893 OS.Process.success)
894 | MsgNo => (print "User does not have trusted path restriction.\n";
895 OS.Process.failure)
896 | MsgError s => (print ("Trusted path query failed: " ^ s ^ "\n");
897 OS.Process.failure)
898 | _ => (print "Unexpected server reply.\n";
899 OS.Process.failure)
900 in
901 loop ()
902 before OpenSSL.close bio
903 end
904
737c68d4
AC
905fun requestSocketPerm {node, uname} =
906 let
907 val (user, context) = requestContext (fn () => ())
94b55bf7 908 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
909 dispatcher
910 else
911 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
737c68d4
AC
912
913 val _ = Msg.send (bio, MsgQuery (QSocket uname))
914
915 fun loop () =
916 case Msg.recv bio of
917 NONE => (print "Server closed connection unexpectedly.\n";
918 OS.Process.failure)
919 | SOME m =>
920 case m of
921 MsgSocket p => (case p of
922 Any => print "Any\n"
923 | Client => print "Client\n"
924 | Server => print "Server\n"
925 | Nada => print "Nada\n";
926 OS.Process.success)
927 | MsgError s => (print ("Socket permission query failed: " ^ s ^ "\n");
928 OS.Process.failure)
929 | _ => (print "Unexpected server reply.\n";
930 OS.Process.failure)
931 in
932 loop ()
933 before OpenSSL.close bio
934 end
935
f9548f16
AC
936fun requestFirewall {node, uname} =
937 let
938 val (user, context) = requestContext (fn () => ())
94b55bf7 939 val bio = OpenSSL.connect true (context, if node = Config.dispatcherName then
8be753d9
AC
940 dispatcher
941 else
942 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
943
167cffff 944 val _ = Msg.send (bio, MsgQuery (QFirewall {node = node, user = uname}))
f9548f16
AC
945
946 fun loop () =
947 case Msg.recv bio of
948 NONE => (print "Server closed connection unexpectedly.\n";
949 OS.Process.failure)
950 | SOME m =>
951 case m of
952 MsgFirewall ls => (app (fn s => (print s; print "\n")) ls;
953 OS.Process.success)
954 | MsgError s => (print ("Firewall query failed: " ^ s ^ "\n");
955 OS.Process.failure)
956 | _ => (print "Unexpected server reply.\n";
957 OS.Process.failure)
958 in
959 loop ()
960 before OpenSSL.close bio
961 end
962
1ffc47a6
AC
963fun requestDescribe dom =
964 let
965 val (_, bio) = requestBio (fn () => ())
966 in
967 Msg.send (bio, MsgDescribe dom);
968 case Msg.recv bio of
969 NONE => print "Server closed connection unexpectedly.\n"
970 | SOME m =>
971 case m of
972 MsgDescription s => print s
973 | MsgError s => print ("Description failed: " ^ s ^ "\n")
974 | _ => print "Unexpected server reply.\n";
975 OpenSSL.close bio
976 end
977
563e7792
AC
978fun requestReUsers () =
979 let
980 val (_, bio) = requestBio (fn () => ())
981 in
982 Msg.send (bio, MsgReUsers);
983 case Msg.recv bio of
984 NONE => print "Server closed connection unexpectedly.\n"
985 | SOME m =>
986 case m of
987 MsgOk => print "Callbacks run.\n"
988 | MsgError s => print ("Failed: " ^ s ^ "\n")
989 | _ => print "Unexpected server reply.\n";
990 OpenSSL.close bio
991 end
992
a8e88df7
CE
993fun requestFirewallRegen node =
994 let
995 val (user, context) = requestContext (fn () => ())
996 val bio = OpenSSL.connect true (context, Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
997 (* Only supporting on slave nodes *)
998
999 val _ = Msg.send (bio, MsgFirewallRegen)
1000
1001 fun handleResult () =
1002 case Msg.recv bio of
1003 NONE => (print "Server closed connection unexpectedly.\n";
1004 OS.Process.failure)
1005 | SOME m =>
1006 case m of
1007 MsgOk => (print "Firewall regenerated.\n";
1008 OS.Process.success)
1009 | MsgError s => (print ("Firewall regeneration failed: " ^ s ^ "\n");
1010 OS.Process.failure)
1011 | _ => (print "Unexpected server reply.\n";
1012 OS.Process.failure)
1013 in
1014 handleResult()
1015 before OpenSSL.close bio
1016 end
1017
1638d5a2
AC
1018structure SS = StringSet
1019
1020fun domainList dname =
1021 let
1022 val dir = Posix.FileSys.opendir dname
1023
1024 fun visitNode dset =
1025 case Posix.FileSys.readdir dir of
1026 NONE => dset
1027 | SOME node =>
1028 let
1029 val path = OS.Path.joinDirFile {dir = dname,
1030 file = node}
1031
1032 fun visitDomains (path, bfor, dset) =
1033 let
1034 val dir = Posix.FileSys.opendir path
1035
1036 fun loop dset =
1037 case Posix.FileSys.readdir dir of
1038 NONE => dset
1039 | SOME dname =>
1040 let
1041 val path = OS.Path.joinDirFile {dir = path,
1042 file = dname}
1043 in
1044 if Posix.FileSys.ST.isDir (Posix.FileSys.stat path) then
1045 let
1046 val bfor = dname :: bfor
1047 in
1048 loop (visitDomains (path, bfor,
1049 SS.add (dset,
1050 String.concatWith "." bfor)))
1051 end
1052 else
1053 loop dset
1054 end
1055 in
1056 loop dset
1057 before Posix.FileSys.closedir dir
1058 end
1059 in
1060 visitNode (visitDomains (path, [], dset))
1061 end
1062 in
1063 visitNode SS.empty
1064 before Posix.FileSys.closedir dir
1065 end
1066
998ed174 1067fun regenerateEither tc checker context =
1824f573 1068 let
76405e1e
AC
1069 val () = print "Starting regeneration....\n"
1070
1071 val domainsBefore =
1072 if tc then
1073 SS.empty
1074 else
1075 domainList Config.resultRoot
1638d5a2 1076
998ed174
AC
1077 fun ifReal f =
1078 if tc then
1079 ()
1080 else
1081 f ()
1082
6f3525e4
AC
1083 val _ = ErrorMsg.reset ()
1084
1824f573 1085 val b = basis ()
71420f8b
AC
1086 val () = Tycheck.disallowExterns ()
1087
1638d5a2
AC
1088 val () = ifReal (fn () =>
1089 (ignore (OS.Process.system ("rm -rf " ^ Config.oldResultRoot ^ "/*"));
1090 ignore (OS.Process.system ("cp -r " ^ Config.resultRoot
1091 ^ "/* " ^ Config.oldResultRoot ^ "/"));
1092 Domain.resetGlobal ()))
71420f8b 1093
fb6fac97
AC
1094 val ok = ref true
1095
c23af445 1096 fun contactNode (node, ip, ipv6) =
201b83c7 1097 if node = Config.dispatcherName then
71420f8b
AC
1098 Domain.resetLocal ()
1099 else let
8be753d9
AC
1100 val bio = OpenSSL.connect true (context,
1101 ip
1102 ^ ":"
1103 ^ Int.toString Config.slavePort)
71420f8b
AC
1104 in
1105 Msg.send (bio, MsgRegenerate);
1106 case Msg.recv bio of
1107 NONE => print "Slave closed connection unexpectedly\n"
1108 | SOME m =>
1109 case m of
1110 MsgOk => print ("Slave " ^ node ^ " pre-regeneration finished\n")
1111 | MsgError s => print ("Slave " ^ node
1112 ^ " returned error: " ^
1113 s ^ "\n")
1114 | _ => print ("Slave " ^ node
1115 ^ " returned unexpected command\n");
1116 OpenSSL.close bio
16465a9a
AC
1117 end
1118 handle OpenSSL.OpenSSL s => print ("OpenSSL error: " ^ s ^ "\n")
1824f573
AC
1119
1120 fun doUser user =
1121 let
1122 val _ = Domain.setUser user
1123 val _ = ErrorMsg.reset ()
1124
1125 val dname = Config.domtoolDir user
fb6fac97
AC
1126 in
1127 if Posix.FileSys.access (dname, []) then
1128 let
1129 val dir = Posix.FileSys.opendir dname
1130
1131 fun loop files =
1132 case Posix.FileSys.readdir dir of
1133 NONE => (Posix.FileSys.closedir dir;
1134 files)
1135 | SOME fname =>
1136 if notTmp fname then
1137 loop (OS.Path.joinDirFile {dir = dname,
1138 file = fname}
1139 :: files)
1140 else
1141 loop files
1824f573 1142
fb6fac97
AC
1143 val files = loop []
1144 val (_, files) = Order.order (SOME b) files
24248d62
AC
1145
1146 fun checker' (file, (G, evs)) =
1147 checker G evs file
fb6fac97
AC
1148 in
1149 if !ErrorMsg.anyErrors then
1150 (ErrorMsg.reset ();
1ffc47a6
AC
1151 print ("User " ^ user ^ "'s configuration has errors!\n");
1152 ok := false)
1824f573 1153 else
76405e1e 1154 ();
e140629f 1155 let val basis' = basis () in
254d5faa 1156 ignore (foldl checker' (basis', SM.empty) files)
e140629f 1157 end
fb6fac97 1158 end
b7c96dd3 1159 else if (String.isSuffix "_admin" user) orelse (String.isSuffix ".daemon" user) then
b1563bce 1160 ()
1824f573 1161 else
b1563bce
AC
1162 (print ("Couldn't access " ^ user ^ "'s ~/.domtool directory.\n");
1163 ok := false)
1824f573 1164 end
f19ba323
AC
1165 handle IO.Io {name, function, ...} =>
1166 (print ("IO error processing user " ^ user ^ ": " ^ function ^ ": " ^ name ^ "\n");
1167 ok := false)
998ed174
AC
1168 | exn as OS.SysErr (s, _) => (print ("System error processing user " ^ user ^ ": " ^ s ^ "\n");
1169 ok := false)
fb6fac97
AC
1170 | ErrorMsg.Error => (ErrorMsg.reset ();
1171 print ("User " ^ user ^ " had a compilation error.\n");
1172 ok := false)
1173 | _ => (print "Unknown exception during regeneration!\n";
1174 ok := false)
1824f573 1175 in
998ed174
AC
1176 ifReal (fn () => (app contactNode Config.nodeIps;
1177 Env.pre ()));
1824f573 1178 app doUser (Acl.users ());
1638d5a2
AC
1179 ifReal (fn () =>
1180 let
1181 val domainsAfter = domainList Config.resultRoot
1182 val domainsGone = SS.difference (domainsBefore, domainsAfter)
1183 in
1184 if SS.isEmpty domainsGone then
1185 ()
1186 else
1187 (print "Domains to kill:";
1188 SS.app (fn s => (print " "; print s)) domainsGone;
1189 print "\n";
1190
1191 Domain.rmdom' Config.oldResultRoot (SS.listItems domainsGone));
1192
1193 Env.post ()
1194 end);
fb6fac97
AC
1195 !ok
1196 end
1197
24248d62
AC
1198val regenerate = regenerateEither false eval
1199val regenerateTc = regenerateEither true
1200 (fn G => fn evs => fn file =>
1201 (#1 (check G file), evs))
1824f573 1202
563e7792
AC
1203fun usersChanged () =
1204 (Domain.onUsersChange ();
1205 ignore (OS.Process.system Config.publish_reusers))
1206
e69e60cc
AC
1207fun rmuser user =
1208 let
1209 val doms = Acl.class {user = user, class = "domain"}
1210 val doms = List.filter (fn dom =>
1211 case Acl.whoHas {class = "domain", value = dom} of
1212 [_] => true
1213 | _ => false) (StringSet.listItems doms)
1214 in
1215 Acl.rmuser user;
563e7792
AC
1216 Domain.rmdom doms;
1217 usersChanged ()
e69e60cc
AC
1218 end
1219
c9731b9b
AC
1220fun now () = Date.toString (Date.fromTimeUniv (Time.now ()))
1221
a95a0107
AC
1222fun answerQuery q =
1223 case q of
1224 QApt pkg => if Apt.installed pkg then MsgYes else MsgNo
f296c496
CE
1225 | QAptExists pkg => (case Apt.info pkg of
1226 SOME {section, description} => MsgAptQuery {section = section, description = description}
1227 | NONE => MsgNo)
d351d679
AC
1228 | QCron user => if Cron.allowed user then MsgYes else MsgNo
1229 | QFtp user => if Ftp.allowed user then MsgYes else MsgNo
4d5126e1 1230 | QTrustedPath user => if TrustedPath.query user then MsgYes else MsgNo
737c68d4 1231 | QSocket user => MsgSocket (SocketPerm.query user)
167cffff 1232 | QFirewall {node, user} => MsgFirewall (Firewall.query (node, user))
a95a0107
AC
1233
1234fun describeQuery q =
1235 case q of
1236 QApt pkg => "Requested installation status of package " ^ pkg
991d8e66 1237 | QAptExists pkg => "Requested if package " ^ pkg ^ " exists"
d351d679
AC
1238 | QCron user => "Asked about cron permissions for user " ^ user
1239 | QFtp user => "Asked about FTP permissions for user " ^ user
4d5126e1 1240 | QTrustedPath user => "Asked about trusted path settings for user " ^ user
737c68d4 1241 | QSocket user => "Asked about socket permissions for user " ^ user
167cffff 1242 | QFirewall {node, user} => "Asked about firewall rules on " ^ node ^ " for user " ^ user
a95a0107 1243
c362e4cc
CE
1244fun doIt' loop bio f cleanup =
1245 ((case f () of
1246 (msgLocal, SOME msgRemote) =>
1247 (print msgLocal;
1248 print "\n";
1249 Msg.send (bio, MsgError msgRemote))
1250 | (msgLocal, NONE) =>
1251 (print msgLocal;
1252 print "\n";
1253 Msg.send (bio, MsgOk)))
1254 handle e as (OpenSSL.OpenSSL s) =>
1255 (print ("OpenSSL error: " ^ s ^ "\n");
1256 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1257 Msg.send (bio, MsgError ("OpenSSL error: " ^ s))
1258 handle OpenSSL.OpenSSL _ => ())
1259 | OS.SysErr (s, _) =>
1260 (print "System error: ";
1261 print s;
1262 print "\n";
1263 Msg.send (bio, MsgError ("System error: " ^ s))
1264 handle OpenSSL.OpenSSL _ => ())
1265 | Fail s =>
1266 (print "Failure: ";
1267 print s;
1268 print "\n";
1269 Msg.send (bio, MsgError ("Failure: " ^ s))
1270 handle OpenSSL.OpenSSL _ => ())
1271 | ErrorMsg.Error =>
1272 (print "Compilation error\n";
1273 Msg.send (bio, MsgError "Error during configuration evaluation")
1274 handle OpenSSL.OpenSSL _ => ());
1275 (cleanup ();
1276 ignore (OpenSSL.readChar bio);
1277 OpenSSL.close bio)
1278 handle OpenSSL.OpenSSL _ => ();
1279 loop ())
1280
3b267643 1281fun service () =
07cc384c 1282 let
edf5dcbb
AC
1283 val host = Slave.hostname ()
1284
aa56e112 1285 val () = Acl.read Config.aclFile
edf5dcbb
AC
1286
1287 val context = context (Config.certDir ^ "/" ^ host ^ ".pem",
1288 Config.keyDir ^ "/" ^ host ^ "/key.pem",
d22c1f00 1289 Config.trustStore)
36e42cb8 1290 val _ = Domain.set_context context
3b267643 1291
60534712 1292 val sock = OpenSSL.listen (context, Config.dispatcherPort)
3b267643
AC
1293
1294 fun loop () =
2ee50226
AC
1295 (case OpenSSL.accept sock of
1296 NONE => ()
1297 | SOME bio =>
1298 let
1299 val user = OpenSSL.peerCN bio
1300 val () = print ("\nConnection from " ^ user ^ " at " ^ now () ^ "\n")
1301 val () = Domain.setUser user
c362e4cc 1302 val doIt = doIt' loop bio
2ee50226
AC
1303
1304 fun doConfig codes =
1305 let
1306 val _ = print "Configuration:\n"
1307 val _ = app (fn s => (print s; print "\n")) codes
1308 val _ = print "\n"
1309
1310 val outname = OS.FileSys.tmpName ()
1311
24248d62 1312 fun doOne (code, (G, evs)) =
2ee50226
AC
1313 let
1314 val outf = TextIO.openOut outname
1315 in
1316 TextIO.output (outf, code);
1317 TextIO.closeOut outf;
24248d62 1318 eval G evs outname
2ee50226
AC
1319 end
1320 in
1321 doIt (fn () => (Env.pre ();
e140629f 1322 let val basis' = basis () in
254d5faa 1323 ignore (foldl doOne (basis', SM.empty) codes)
e140629f 1324 end;
2ee50226
AC
1325 Env.post ();
1326 Msg.send (bio, MsgOk);
1327 ("Configuration complete.", NONE)))
1328 (fn () => OS.FileSys.remove outname)
1329 end
1330
1331 fun checkAddr s =
1332 case String.fields (fn ch => ch = #"@") s of
1333 [user'] =>
1334 if user = user' then
1335 SOME (SetSA.User s)
1336 else
1337 NONE
1338 | [user', domain] =>
1339 if Domain.validEmailUser user' andalso Domain.yourDomain domain then
1340 SOME (SetSA.Email s)
1341 else
1342 NONE
1343 | _ => NONE
1344
1345 fun cmdLoop () =
1346 case Msg.recv bio of
1347 NONE => (OpenSSL.close bio
1348 handle OpenSSL.OpenSSL _ => ();
1349 loop ())
1350 | SOME m =>
1351 case m of
1352 MsgConfig code => doConfig [code]
1353 | MsgMultiConfig codes => doConfig codes
1354
1355 | MsgShutdown =>
1356 if Acl.query {user = user, class = "priv", value = "all"}
1357 orelse Acl.query {user = user, class = "priv", value = "shutdown"} then
1358 print ("Domtool dispatcher shutting down at " ^ now () ^ "\n\n")
1359 else
1360 (print "Unauthorized shutdown command!\n";
1361 OpenSSL.close bio
1362 handle OpenSSL.OpenSSL _ => ();
1363 loop ())
1364
1365 | MsgGrant acl =>
1366 doIt (fn () =>
1367 if Acl.query {user = user, class = "priv", value = "all"} then
1368 (Acl.grant acl;
1369 Acl.write Config.aclFile;
563e7792
AC
1370 if #class acl = "user" then
1371 usersChanged ()
1372 else
1373 ();
2ee50226
AC
1374 ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".",
1375 NONE))
1376 else
1377 ("Unauthorized user asked to grant a permission!",
1378 SOME "Not authorized to grant privileges"))
1379 (fn () => ())
1380
1381 | MsgRevoke acl =>
1382 doIt (fn () =>
1383 if Acl.query {user = user, class = "priv", value = "all"} then
1384 (Acl.revoke acl;
1385 Acl.write Config.aclFile;
1386 ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".",
1387 NONE))
1388 else
1389 ("Unauthorized user asked to revoke a permission!",
1390 SOME "Not authorized to revoke privileges"))
1391 (fn () => ())
1392
1393 | MsgListPerms user =>
1394 doIt (fn () =>
1395 (Msg.send (bio, MsgPerms (Acl.queryAll user));
1396 ("Sent permission list for user " ^ user ^ ".",
1397 NONE)))
1398 (fn () => ())
1399
1400 | MsgWhoHas perm =>
1401 doIt (fn () =>
1402 (Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
1403 ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".",
1404 NONE)))
1405 (fn () => ())
1406
1407 | MsgRmdom doms =>
1408 doIt (fn () =>
1409 if Acl.query {user = user, class = "priv", value = "all"}
51cc45f7
AC
1410 orelse List.all (fn dom => Domain.validDomain dom
1411 andalso Acl.queryDomain {user = user, domain = dom}) doms then
2ee50226 1412 (Domain.rmdom doms;
284f3883 1413 (*app (fn dom =>
2ee50226 1414 Acl.revokeFromAll {class = "domain", value = dom}) doms;
284f3883 1415 Acl.write Config.aclFile;*)
2ee50226
AC
1416 ("Removed domains" ^ foldl (fn (d, s) => s ^ " " ^ d) "" doms ^ ".",
1417 NONE))
1418 else
1419 ("Unauthorized user asked to remove a domain!",
1420 SOME "Not authorized to remove that domain"))
1421 (fn () => ())
1422
1423 | MsgRegenerate =>
1424 doIt (fn () =>
1425 if Acl.query {user = user, class = "priv", value = "regen"}
1426 orelse Acl.query {user = user, class = "priv", value = "all"} then
1427 (if regenerate context then
1428 ("Regenerated all configuration.",
1429 NONE)
1430 else
1431 ("Error regenerating configuration!",
1432 SOME "Error regenerating configuration! Consult /var/log/domtool.log."))
1433 else
1434 ("Unauthorized user asked to regenerate!",
1435 SOME "Not authorized to regenerate"))
1436 (fn () => ())
1437
1438 | MsgRegenerateTc =>
1439 doIt (fn () =>
1440 if Acl.query {user = user, class = "priv", value = "regen"}
1441 orelse Acl.query {user = user, class = "priv", value = "all"} then
1442 (if regenerateTc context then
1443 ("Checked all configuration.",
1444 NONE)
1445 else
1446 ("Found a compilation error!",
1447 SOME "Found a compilation error! Consult /var/log/domtool.log."))
1448 else
1449 ("Unauthorized user asked to regenerate -tc!",
1450 SOME "Not authorized to regenerate -tc"))
1451 (fn () => ())
1452
1453 | MsgRmuser user' =>
1454 doIt (fn () =>
1455 if Acl.query {user = user, class = "priv", value = "all"} then
1456 (rmuser user';
1457 Acl.write Config.aclFile;
1458 ("Removed user " ^ user' ^ ".",
1459 NONE))
1460 else
1461 ("Unauthorized user asked to remove a user!",
1462 SOME "Not authorized to remove users"))
1463 (fn () => ())
1464
2ee50226
AC
1465 | MsgListMailboxes domain =>
1466 doIt (fn () =>
1467 if not (Domain.yourDomain domain) then
1468 ("User wasn't authorized to list mailboxes for " ^ domain,
1469 SOME "You're not authorized to configure that domain.")
1470 else
1471 case Vmail.list domain of
1472 Vmail.Listing users => (Msg.send (bio, MsgMailboxes users);
1473 ("Sent mailbox list for " ^ domain,
1474 NONE))
1475 | Vmail.Error msg => ("Error listing mailboxes for " ^ domain ^ ": " ^ msg,
1476 SOME msg))
1477 (fn () => ())
1478
1479 | MsgNewMailbox {domain, user = emailUser, passwd, mailbox} =>
1480 doIt (fn () =>
1481 if not (Domain.yourDomain domain) then
1482 ("User wasn't authorized to add a mailbox to " ^ domain,
1483 SOME "You're not authorized to configure that domain.")
1484 else if not (Domain.validEmailUser emailUser) then
1485 ("Invalid e-mail username " ^ emailUser,
1486 SOME "Invalid e-mail username")
1487 else if not (CharVector.all Char.isGraph passwd) then
1488 ("Invalid password",
1489 SOME "Invalid password; may only contain printable, non-space characters")
1490 else if not (Domain.yourPath mailbox) then
1491 ("User wasn't authorized to add a mailbox at " ^ mailbox,
3bf720f7
AC
1492 SOME ("You're not authorized to use that mailbox location. ("
1493 ^ mailbox ^ ")"))
2ee50226
AC
1494 else
1495 case Vmail.add {requester = user,
1496 domain = domain, user = emailUser,
1497 passwd = passwd, mailbox = mailbox} of
1498 NONE => ("Added mailbox " ^ emailUser ^ "@" ^ domain ^ " at " ^ mailbox,
1499 NONE)
1500 | SOME msg => ("Error adding mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1501 SOME msg))
1502 (fn () => ())
1503
1504 | MsgPasswdMailbox {domain, user = emailUser, passwd} =>
1505 doIt (fn () =>
1506 if not (Domain.yourDomain domain) then
1507 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1508 SOME "You're not authorized to configure that domain.")
1509 else if not (Domain.validEmailUser emailUser) then
1510 ("Invalid e-mail username " ^ emailUser,
1511 SOME "Invalid e-mail username")
1512 else if not (CharVector.all Char.isGraph passwd) then
1513 ("Invalid password",
1514 SOME "Invalid password; may only contain printable, non-space characters")
1515 else
1516 case Vmail.passwd {domain = domain, user = emailUser,
1517 passwd = passwd} of
1518 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
1519 NONE)
1520 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1521 SOME msg))
1522 (fn () => ())
1523
0e0442b0
CE
1524 | MsgPortalPasswdMailbox {domain, user = emailUser, oldpasswd, newpasswd} =>
1525 doIt (fn () =>
1526 if not (Acl.query {user = user, class = "priv", value = "vmail"}) then
1527 ("User is not authorized to run portal vmail password",
1528 SOME "You're not authorized to use the portal password command")
1529 else if not (Domain.validEmailUser emailUser) then
1530 ("Invalid e-mail username " ^ emailUser,
1531 SOME "Invalid e-mail username")
1532 else if not (CharVector.all Char.isGraph oldpasswd
1533 andalso CharVector.all Char.isGraph newpasswd) then
1534 ("Invalid password",
1535 SOME "Invalid password; may only contain printable, non-space characters")
1536 else
1537 case Vmail.portalpasswd {domain = domain, user = emailUser,
1538 oldpasswd = oldpasswd, newpasswd = newpasswd} of
1539 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
1540 NONE)
1541 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1542 SOME msg))
1543 (fn () => ())
1544
2ee50226
AC
1545 | MsgRmMailbox {domain, user = emailUser} =>
1546 doIt (fn () =>
1547 if not (Domain.yourDomain domain) then
1548 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1549 SOME "You're not authorized to configure that domain.")
1550 else if not (Domain.validEmailUser emailUser) then
1551 ("Invalid e-mail username " ^ emailUser,
1552 SOME "Invalid e-mail username")
1553 else
1554 case Vmail.rm {domain = domain, user = emailUser} of
1555 NONE => ("Deleted mailbox " ^ emailUser ^ "@" ^ domain,
1556 NONE)
1557 | SOME msg => ("Error deleting mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1558 SOME msg))
1559 (fn () => ())
1560
1561 | MsgSaQuery addr =>
1562 doIt (fn () =>
1563 case checkAddr addr of
1564 NONE => ("User tried to query SA filtering for " ^ addr,
1565 SOME "You aren't allowed to configure SA filtering for that recipient.")
1566 | SOME addr' => (Msg.send (bio, MsgSaStatus (SetSA.query addr'));
1567 ("Queried SA filtering status for " ^ addr,
1568 NONE)))
1569 (fn () => ())
1570
1571 | MsgSaSet (addr, b) =>
1572 doIt (fn () =>
1573 case checkAddr addr of
1574 NONE => ("User tried to set SA filtering for " ^ addr,
1575 SOME "You aren't allowed to configure SA filtering for that recipient.")
1576 | SOME addr' => (SetSA.set (addr', b);
ebb51f80 1577 SetSA.rebuild ();
2ee50226
AC
1578 Msg.send (bio, MsgOk);
1579 ("Set SA filtering status for " ^ addr ^ " to "
1580 ^ (if b then "ON" else "OFF"),
1581 NONE)))
1582 (fn () => ())
1583
1584 | MsgSmtpLogReq domain =>
1585 doIt (fn () =>
1586 if not (Domain.yourDomain domain) then
1587 ("Unauthorized user tried to request SMTP logs for " ^ domain,
1588 SOME "You aren't authorized to configure that domain.")
1589 else
1590 (SmtpLog.search (fn line => Msg.send (bio, MsgSmtpLogRes line))
1591 domain;
1592 ("Requested SMTP logs for " ^ domain,
1593 NONE)))
1594 (fn () => ())
1595
1596 | MsgQuery q =>
1597 doIt (fn () => (Msg.send (bio, answerQuery q);
1598 (describeQuery q,
1599 NONE)))
1600 (fn () => ())
1ffc47a6
AC
1601 | MsgDescribe dom =>
1602 doIt (fn () => if not (Domain.validDomain dom) then
1603 ("Requested description of invalid domain " ^ dom,
1604 SOME "Invalid domain name")
1605 else if not (Domain.yourDomain dom
1606 orelse Acl.query {user = user, class = "priv", value = "all"}) then
1607 ("Requested description of " ^ dom ^ ", but not allowed access",
1608 SOME "Access denied")
1609 else
1610 (Msg.send (bio, MsgDescription (Domain.describe dom));
1611 ("Sent description of domain " ^ dom,
1612 NONE)))
1613 (fn () => ())
1614
563e7792 1615 | MsgReUsers =>
072a71cf
AC
1616 doIt (fn () => if Acl.query {user = user, class = "priv", value = "regen"}
1617 orelse Acl.query {user = user, class = "priv", value = "all"} then
1618 (usersChanged ();
1619 ("Users change callbacks run", NONE))
1620 else
1621 ("Unauthorized user asked to reusers!",
1622 SOME "You aren't authorized to regenerate files."))
563e7792
AC
1623 (fn () => ())
1624
2ee50226
AC
1625 | _ =>
1626 doIt (fn () => ("Unexpected command",
1627 SOME "Unexpected command"))
1628 (fn () => ())
1629 in
1630 cmdLoop ()
1631 end
1632 handle e as (OpenSSL.OpenSSL s) =>
1633 (print ("OpenSSL error: " ^ s ^ "\n");
1634 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1635 OpenSSL.close bio
08688401
AC
1636 handle OpenSSL.OpenSSL _ => ();
1637 loop ())
2ee50226
AC
1638 | OS.SysErr (s, _) =>
1639 (print ("System error: " ^ s ^ "\n");
1640 OpenSSL.close bio
1641 handle OpenSSL.OpenSSL _ => ();
1642 loop ())
1643 | IO.Io {name, function, cause} =>
1644 (print ("IO error: " ^ function ^ " for " ^ name ^ "\n");
1645 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory cause);
1646 OpenSSL.close bio
1647 handle OpenSSL.OpenSSL _ => ();
1648 loop ())
314ce7bd
AC
1649 | OS.Path.InvalidArc =>
1650 (print "Invalid arc\n";
1651 OpenSSL.close bio
1652 handle OpenSSL.OpenSSL _ => ();
1653 loop ())
2ee50226
AC
1654 | e =>
1655 (print "Unknown exception in main loop!\n";
1656 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1657 OpenSSL.close bio
1658 handle OpenSSL.OpenSSL _ => ();
1659 loop ()))
1660 handle e as (OpenSSL.OpenSSL s) =>
1661 (print ("OpenSSL error: " ^ s ^ "\n");
1662 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1663 loop ())
1664 | OS.SysErr (s, _) =>
1665 (print ("System error: " ^ s ^ "\n");
1666 loop ())
1667 | IO.Io {name, function, cause} =>
1668 (print ("IO error: " ^ function ^ " for " ^ name ^ "\n");
1669 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory cause);
1670 loop ())
1671 | e =>
1672 (print "Unknown exception in main loop!\n";
1673 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1674 loop ())
36e42cb8 1675 in
c9731b9b 1676 print ("Domtool dispatcher starting up at " ^ now () ^ "\n");
361a1e7f 1677 print "Listening for connections....\n";
36e42cb8
AC
1678 loop ();
1679 OpenSSL.shutdown sock
1680 end
1681
1682fun slave () =
1683 let
6e62228d 1684 val host = Slave.hostname ()
36e42cb8 1685
d22c1f00
AC
1686 val context = context (Config.certDir ^ "/" ^ host ^ ".pem",
1687 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1688 Config.trustStore)
36e42cb8
AC
1689
1690 val sock = OpenSSL.listen (context, Config.slavePort)
1691
c9731b9b
AC
1692 val _ = print ("Slave server starting at " ^ now () ^ "\n")
1693
36e42cb8 1694 fun loop () =
4f3ef3c5 1695 (case OpenSSL.accept sock of
4f5a3f95
AC
1696 NONE => ()
1697 | SOME bio =>
1698 let
1699 val peer = OpenSSL.peerCN bio
1700 val () = print ("\nConnection from " ^ peer ^ " at " ^ now () ^ "\n")
1701 in
1702 if peer = Config.dispatcherName then let
1703 fun loop' files =
1704 case Msg.recv bio of
1705 NONE => print "Dispatcher closed connection unexpectedly\n"
1706 | SOME m =>
1707 case m of
1708 MsgFile file => loop' (file :: files)
1709 | MsgDoFiles => (Slave.handleChanges files;
1710 Msg.send (bio, MsgOk))
1711 | MsgRegenerate => (Domain.resetLocal ();
1712 Msg.send (bio, MsgOk))
9b8c6dc8
AC
1713 | MsgVmailChanged => (if Vmail.doChanged () then
1714 Msg.send (bio, MsgOk)
1715 else
1716 Msg.send (bio, MsgError "userdb update failed"))
ebb51f80
CE
1717 | MsgSaChanged => (if Slave.shell [Config.SpamAssassin.postReload] then
1718 Msg.send (bio, MsgOk)
1719 else
1720 Msg.send (bio, MsgError "Error reloading SpamAssassin addresses"))
4f5a3f95
AC
1721 | _ => (print "Dispatcher sent unexpected command\n";
1722 Msg.send (bio, MsgError "Unexpected command"))
1723 in
1724 loop' [];
1725 ignore (OpenSSL.readChar bio);
1726 OpenSSL.close bio;
1727 loop ()
1728 end
1729 else if peer = "domtool" then
1730 case Msg.recv bio of
1731 SOME MsgShutdown => (OpenSSL.close bio;
1732 print ("Shutting down at " ^ now () ^ "\n\n"))
1733 | _ => (OpenSSL.close bio;
1734 loop ())
1735 else
c362e4cc
CE
1736 let
1737 val doIt = doIt' loop bio
1738 val user = peer
1739 in
1740 case Msg.recv bio of
1741 NONE => (OpenSSL.close bio
1742 handle OpenSSL.OpenSSL _ => ();
1743 loop ())
1744 | SOME m =>
1745 case m of
1746 (MsgQuery q) => (print (describeQuery q ^ "\n");
1747 Msg.send (bio, answerQuery q);
1748 ignore (OpenSSL.readChar bio);
1749 OpenSSL.close bio;
1750 loop ())
1751 | MsgCreateDbUser {dbtype, passwd} =>
1752 doIt (fn () =>
1753 case Dbms.lookup dbtype of
1754 NONE => ("Database user creation request with unknown datatype type " ^ dbtype,
1755 SOME ("Unknown database type " ^ dbtype))
1756 | SOME handler =>
1757 case #adduser handler {user = user, passwd = passwd} of
1758 NONE => ("Added " ^ dbtype ^ " user " ^ user ^ ".",
1759 NONE)
1760 | SOME msg =>
1761 ("Error adding a " ^ dbtype ^ " user " ^ user ^ ": " ^ msg,
1762 SOME ("Error adding user: " ^ msg)))
1763 (fn () => ())
1764
1765 | MsgDbPasswd {dbtype, passwd} =>
1766 doIt (fn () =>
1767 case Dbms.lookup dbtype of
1768 NONE => ("Database passwd request with unknown datatype type " ^ dbtype,
1769 SOME ("Unknown database type " ^ dbtype))
1770 | SOME handler =>
1771 case #passwd handler {user = user, passwd = passwd} of
1772 NONE => ("Changed " ^ dbtype ^ " password of user " ^ user ^ ".",
1773 NONE)
1774 | SOME msg =>
1775 ("Error setting " ^ dbtype ^ " password of user " ^ user ^ ": " ^ msg,
1776 SOME ("Error adding user: " ^ msg)))
1777 (fn () => ())
1778
1779 | MsgCreateDb {dbtype, dbname, encoding} =>
1780 doIt (fn () =>
1781 if Dbms.validDbname dbname then
1782 case Dbms.lookup dbtype of
1783 NONE => ("Database creation request with unknown datatype type " ^ dbtype,
1784 SOME ("Unknown database type " ^ dbtype))
1785 | SOME handler =>
1786 if not (Dbms.validEncoding encoding) then
1787 ("Invalid encoding " ^ valOf encoding ^ " requested for database creation.",
1788 SOME "Invalid encoding")
1789 else
1790 case #createdb handler {user = user, dbname = dbname, encoding = encoding} of
1791 NONE => ("Created database " ^ user ^ "_" ^ dbname ^ ".",
1792 NONE)
1793 | SOME msg => ("Error creating database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1794 SOME ("Error creating database: " ^ msg))
1795 else
1796 ("Invalid database name " ^ user ^ "_" ^ dbname,
1797 SOME ("Invalid database name " ^ dbname)))
1798 (fn () => ())
1799
1800 | MsgDropDb {dbtype, dbname} =>
1801 doIt (fn () =>
1802 if Dbms.validDbname dbname then
1803 case Dbms.lookup dbtype of
1804 NONE => ("Database drop request with unknown datatype type " ^ dbtype,
1805 SOME ("Unknown database type " ^ dbtype))
1806 | SOME handler =>
1807 case #dropdb handler {user = user, dbname = dbname} of
1808 NONE => ("Drop database " ^ user ^ "_" ^ dbname ^ ".",
1809 NONE)
1810 | SOME msg => ("Error dropping database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1811 SOME ("Error dropping database: " ^ msg))
1812 else
1813 ("Invalid database name " ^ user ^ "_" ^ dbname,
1814 SOME ("Invalid database name " ^ dbname)))
1815 (fn () => ())
1816
1817 | MsgGrantDb {dbtype, dbname} =>
1818 doIt (fn () =>
1819 if Dbms.validDbname dbname then
1820 case Dbms.lookup dbtype of
1821 NONE => ("Database drop request with unknown datatype type " ^ dbtype,
1822 SOME ("Unknown database type " ^ dbtype))
1823 | SOME handler =>
1824 case #grant handler {user = user, dbname = dbname} of
1825 NONE => ("Grant permissions to database " ^ user ^ "_" ^ dbname ^ ".",
1826 NONE)
1827 | SOME msg => ("Error granting permissions to database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1828 SOME ("Error granting permissions to database: " ^ msg))
1829 else
1830 ("Invalid database name " ^ user ^ "_" ^ dbname,
1831 SOME ("Invalid database name " ^ dbname)))
1832 (fn () => ())
73b95423 1833 | MsgFirewallRegen =>
4f3ef3c5
CE
1834 doIt (fn () => (Acl.read Config.aclFile;
1835 if Acl.query {user = user, class = "priv", value = "all"} then
1836 if List.exists (fn x => x = host) Config.Firewall.firewallNodes then
1837 if (Firewall.generateFirewallConfig (Firewall.parseRules ()) andalso Firewall.publishConfig ())
1838 then
1839 ("Firewall rules regenerated.", NONE)
1840 else
1b96e27d
CE
1841 ("Rules regeneration failed!", SOME "Script execution failed.")
1842 else ("Node not controlled by domtool firewall.", SOME (host))
4f3ef3c5
CE
1843 else
1844 ("Not authorized to regenerate firewall.", SOME ("Unauthorized user " ^ user ^ " attempted to regenerated firewall"))))
73b95423 1845 (fn () => ())
caba7e27 1846
c362e4cc
CE
1847 | _ => (OpenSSL.close bio;
1848 loop ())
1849 end
4f5a3f95
AC
1850 end handle OpenSSL.OpenSSL s =>
1851 (print ("OpenSSL error: " ^ s ^ "\n");
1852 OpenSSL.close bio
1853 handle OpenSSL.OpenSSL _ => ();
1854 loop ())
1855 | e as OS.SysErr (s, _) =>
1856 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1857 print ("System error: "^ s ^ "\n");
1858 OpenSSL.close bio
1859 handle OpenSSL.OpenSSL _ => ();
1860 loop ())
1861 | IO.Io {function, name, ...} =>
1862 (print ("IO error: " ^ function ^ ": " ^ name ^ "\n");
1863 OpenSSL.close bio
1864 handle OpenSSL.OpenSSL _ => ();
1865 loop ())
1866 | e =>
1867 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1868 print "Uncaught exception!\n";
1869 OpenSSL.close bio
1870 handle OpenSSL.OpenSSL _ => ();
1871 loop ()))
1872 handle OpenSSL.OpenSSL s =>
1873 (print ("OpenSSL error: " ^ s ^ "\n");
1874 loop ())
1875 | e =>
1876 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1877 print "Uncaught exception!\n";
1878 loop ())
07cc384c 1879 in
3b267643
AC
1880 loop ();
1881 OpenSSL.shutdown sock
07cc384c
AC
1882 end
1883
44a5ce2f 1884fun listBasis () =
3196000d
AC
1885 let
1886 val dir = Posix.FileSys.opendir Config.libRoot
1887
1888 fun loop files =
1889 case Posix.FileSys.readdir dir of
1890 NONE => (Posix.FileSys.closedir dir;
1891 files)
1892 | SOME fname =>
1893 if String.isSuffix ".dtl" fname then
1894 loop (OS.Path.joinDirFile {dir = Config.libRoot,
1895 file = fname}
1896 :: files)
1897 else
1898 loop files
3196000d 1899 in
44a5ce2f 1900 loop []
3196000d
AC
1901 end
1902
44a5ce2f
AC
1903fun autodocBasis outdir =
1904 Autodoc.autodoc {outdir = outdir, infiles = listBasis ()}
1905
234b917a 1906end