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