Add shutdown command
[hcoop/domtool2.git] / src / main.sml
... / ...
CommitLineData
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *)
18
19(* Main interface *)
20
21structure Main :> MAIN = struct
22
23open Ast MsgTypes Print
24
25structure SM = StringMap
26
27fun init () = Acl.read Config.aclFile
28
29fun check' G fname =
30 let
31 val prog = Parse.parse fname
32 in
33 if !ErrorMsg.anyErrors then
34 G
35 else
36 Tycheck.checkFile G (Defaults.tInit ()) prog
37 end
38
39fun basis () =
40 let
41 val dir = Posix.FileSys.opendir Config.libRoot
42
43 fun loop files =
44 case Posix.FileSys.readdir dir of
45 NONE => (Posix.FileSys.closedir dir;
46 files)
47 | SOME fname =>
48 if String.isSuffix ".dtl" fname then
49 loop (OS.Path.joinDirFile {dir = Config.libRoot,
50 file = fname}
51 :: files)
52 else
53 loop files
54
55 val files = loop []
56 val (_, files) = Order.order NONE files
57 in
58 if !ErrorMsg.anyErrors then
59 Env.empty
60 else
61 (Tycheck.allowExterns ();
62 foldl (fn (fname, G) => check' G fname) Env.empty files
63 before Tycheck.disallowExterns ())
64 end
65
66fun check fname =
67 let
68 val _ = ErrorMsg.reset ()
69 val _ = Env.preTycheck ()
70
71 val b = basis ()
72 in
73 if !ErrorMsg.anyErrors then
74 raise ErrorMsg.Error
75 else
76 let
77 val _ = Tycheck.disallowExterns ()
78 val _ = ErrorMsg.reset ()
79 val prog = Parse.parse fname
80 in
81 if !ErrorMsg.anyErrors then
82 raise ErrorMsg.Error
83 else
84 let
85 val G' = Tycheck.checkFile b (Defaults.tInit ()) prog
86 in
87 if !ErrorMsg.anyErrors then
88 raise ErrorMsg.Error
89 else
90 (G', #3 prog)
91 end
92 end
93 end
94
95val notTmp = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"_" orelse ch = #"-")
96
97fun checkDir dname =
98 let
99 val b = basis ()
100
101 val dir = Posix.FileSys.opendir dname
102
103 fun loop files =
104 case Posix.FileSys.readdir dir of
105 NONE => (Posix.FileSys.closedir dir;
106 files)
107 | SOME fname =>
108 if notTmp fname then
109 loop (OS.Path.joinDirFile {dir = dname,
110 file = fname}
111 :: files)
112 else
113 loop files
114
115 val files = loop []
116 val (_, files) = Order.order (SOME b) files
117 in
118 if !ErrorMsg.anyErrors then
119 raise ErrorMsg.Error
120 else
121 (foldl (fn (fname, G) => check' G fname) b files;
122 if !ErrorMsg.anyErrors then
123 raise ErrorMsg.Error
124 else
125 ())
126 end
127
128fun reduce fname =
129 let
130 val (G, body) = check fname
131 in
132 if !ErrorMsg.anyErrors then
133 NONE
134 else
135 case body of
136 SOME body =>
137 let
138 val body' = Reduce.reduceExp G body
139 in
140 (*printd (PD.hovBox (PD.PPS.Rel 0,
141 [PD.string "Result:",
142 PD.space 1,
143 p_exp body']))*)
144 SOME body'
145 end
146 | _ => NONE
147 end
148
149fun eval fname =
150 case reduce fname of
151 (SOME body') =>
152 if !ErrorMsg.anyErrors then
153 raise ErrorMsg.Error
154 else
155 Eval.exec (Defaults.eInit ()) body'
156 | NONE => raise ErrorMsg.Error
157
158fun eval' fname =
159 case reduce fname of
160 (SOME body') =>
161 if !ErrorMsg.anyErrors then
162 raise ErrorMsg.Error
163 else
164 ignore (Eval.exec' (Defaults.eInit ()) body')
165 | NONE => raise ErrorMsg.Error
166
167val dispatcher =
168 Config.dispatcher ^ ":" ^ Int.toString Config.dispatcherPort
169
170fun requestContext f =
171 let
172 val uid = Posix.ProcEnv.getuid ()
173 val user = Posix.SysDB.Passwd.name (Posix.SysDB.getpwuid uid)
174
175 val () = Acl.read Config.aclFile
176 val () = Domain.setUser user
177
178 val () = f ()
179
180 val context = OpenSSL.context (Config.certDir ^ "/" ^ user ^ ".pem",
181 Config.keyDir ^ "/" ^ user ^ "/key.pem",
182 Config.trustStore)
183 in
184 (user, context)
185 end
186
187fun requestBio f =
188 let
189 val (user, context) = requestContext f
190 in
191 (user, OpenSSL.connect (context, dispatcher))
192 end
193
194fun request fname =
195 let
196 val (user, bio) = requestBio (fn () => ignore (check fname))
197
198 val inf = TextIO.openIn fname
199
200 fun loop lines =
201 case TextIO.inputLine inf of
202 NONE => String.concat (List.rev lines)
203 | SOME line => loop (line :: lines)
204
205 val code = loop []
206 in
207 TextIO.closeIn inf;
208 Msg.send (bio, MsgConfig code);
209 case Msg.recv bio of
210 NONE => print "Server closed connection unexpectedly.\n"
211 | SOME m =>
212 case m of
213 MsgOk => print "Configuration succeeded.\n"
214 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
215 | _ => print "Unexpected server reply.\n";
216 OpenSSL.close bio
217 end
218 handle ErrorMsg.Error => ()
219
220fun requestDir dname =
221 let
222 val _ = ErrorMsg.reset ()
223
224 val (user, bio) = requestBio (fn () => checkDir dname)
225
226 val b = basis ()
227
228 val dir = Posix.FileSys.opendir dname
229
230 fun loop files =
231 case Posix.FileSys.readdir dir of
232 NONE => (Posix.FileSys.closedir dir;
233 files)
234 | SOME fname =>
235 if notTmp fname then
236 loop (OS.Path.joinDirFile {dir = dname,
237 file = fname}
238 :: files)
239 else
240 loop files
241
242 val files = loop []
243 val (_, files) = Order.order (SOME b) files
244
245 val _ = if !ErrorMsg.anyErrors then
246 raise ErrorMsg.Error
247 else
248 ()
249
250 val codes = map (fn fname =>
251 let
252 val inf = TextIO.openIn fname
253
254 fun loop lines =
255 case TextIO.inputLine inf of
256 NONE => String.concat (rev lines)
257 | SOME line => loop (line :: lines)
258 in
259 loop []
260 before TextIO.closeIn inf
261 end) files
262 in
263 if !ErrorMsg.anyErrors then
264 ()
265 else
266 (Msg.send (bio, MsgMultiConfig codes);
267 case Msg.recv bio of
268 NONE => print "Server closed connection unexpectedly.\n"
269 | SOME m =>
270 case m of
271 MsgOk => print "Configuration succeeded.\n"
272 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
273 | _ => print "Unexpected server reply.\n";
274 OpenSSL.close bio)
275 end
276 handle ErrorMsg.Error => ()
277
278fun requestShutdown () =
279 let
280 val (_, bio) = requestBio (fn () => ())
281 in
282 Msg.send (bio, MsgShutdown);
283 case Msg.recv bio of
284 NONE => print "Server closed connection unexpectedly.\n"
285 | SOME m =>
286 case m of
287 MsgOk => print "Shutdown begun.\n"
288 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
289 | _ => print "Unexpected server reply.\n";
290 OpenSSL.close bio
291 end
292
293fun requestGrant acl =
294 let
295 val (user, bio) = requestBio (fn () => ())
296 in
297 Msg.send (bio, MsgGrant acl);
298 case Msg.recv bio of
299 NONE => print "Server closed connection unexpectedly.\n"
300 | SOME m =>
301 case m of
302 MsgOk => print "Grant succeeded.\n"
303 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
304 | _ => print "Unexpected server reply.\n";
305 OpenSSL.close bio
306 end
307
308fun requestRevoke acl =
309 let
310 val (user, bio) = requestBio (fn () => ())
311 in
312 Msg.send (bio, MsgRevoke acl);
313 case Msg.recv bio of
314 NONE => print "Server closed connection unexpectedly.\n"
315 | SOME m =>
316 case m of
317 MsgOk => print "Revoke succeeded.\n"
318 | MsgError s => print ("Revoke failed: " ^ s ^ "\n")
319 | _ => print "Unexpected server reply.\n";
320 OpenSSL.close bio
321 end
322
323fun requestListPerms user =
324 let
325 val (_, bio) = requestBio (fn () => ())
326 in
327 Msg.send (bio, MsgListPerms user);
328 (case Msg.recv bio of
329 NONE => (print "Server closed connection unexpectedly.\n";
330 NONE)
331 | SOME m =>
332 case m of
333 MsgPerms perms => SOME perms
334 | MsgError s => (print ("Listing failed: " ^ s ^ "\n");
335 NONE)
336 | _ => (print "Unexpected server reply.\n";
337 NONE))
338 before OpenSSL.close bio
339 end
340
341fun requestWhoHas perm =
342 let
343 val (_, bio) = requestBio (fn () => ())
344 in
345 Msg.send (bio, MsgWhoHas perm);
346 (case Msg.recv bio of
347 NONE => (print "Server closed connection unexpectedly.\n";
348 NONE)
349 | SOME m =>
350 case m of
351 MsgWhoHasResponse users => SOME users
352 | MsgError s => (print ("whohas failed: " ^ s ^ "\n");
353 NONE)
354 | _ => (print "Unexpected server reply.\n";
355 NONE))
356 before OpenSSL.close bio
357 end
358
359fun requestRegen () =
360 let
361 val (_, bio) = requestBio (fn () => ())
362 in
363 Msg.send (bio, MsgRegenerate);
364 case Msg.recv bio of
365 NONE => print "Server closed connection unexpectedly.\n"
366 | SOME m =>
367 case m of
368 MsgOk => print "Regeneration succeeded.\n"
369 | MsgError s => print ("Regeneration failed: " ^ s ^ "\n")
370 | _ => print "Unexpected server reply.\n";
371 OpenSSL.close bio
372 end
373
374fun requestRmdom dom =
375 let
376 val (_, bio) = requestBio (fn () => ())
377 in
378 Msg.send (bio, MsgRmdom dom);
379 case Msg.recv bio of
380 NONE => print "Server closed connection unexpectedly.\n"
381 | SOME m =>
382 case m of
383 MsgOk => print "Removal succeeded.\n"
384 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
385 | _ => print "Unexpected server reply.\n";
386 OpenSSL.close bio
387 end
388
389fun requestRmuser user =
390 let
391 val (_, bio) = requestBio (fn () => ())
392 in
393 Msg.send (bio, MsgRmuser user);
394 case Msg.recv bio of
395 NONE => print "Server closed connection unexpectedly.\n"
396 | SOME m =>
397 case m of
398 MsgOk => print "Removal succeeded.\n"
399 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
400 | _ => print "Unexpected server reply.\n";
401 OpenSSL.close bio
402 end
403
404fun requestDbUser dbtype =
405 let
406 val (_, bio) = requestBio (fn () => ())
407 in
408 Msg.send (bio, MsgCreateDbUser dbtype);
409 case Msg.recv bio of
410 NONE => print "Server closed connection unexpectedly.\n"
411 | SOME m =>
412 case m of
413 MsgOk => print "Your user has been created.\n"
414 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
415 | _ => print "Unexpected server reply.\n";
416 OpenSSL.close bio
417 end
418
419fun requestDbPasswd rc =
420 let
421 val (_, bio) = requestBio (fn () => ())
422 in
423 Msg.send (bio, MsgDbPasswd rc);
424 case Msg.recv bio of
425 NONE => print "Server closed connection unexpectedly.\n"
426 | SOME m =>
427 case m of
428 MsgOk => print "Your password has been changed.\n"
429 | MsgError s => print ("Password set failed: " ^ s ^ "\n")
430 | _ => print "Unexpected server reply.\n";
431 OpenSSL.close bio
432 end
433
434fun requestDbTable p =
435 let
436 val (user, bio) = requestBio (fn () => ())
437 in
438 Msg.send (bio, MsgCreateDbTable p);
439 case Msg.recv bio of
440 NONE => print "Server closed connection unexpectedly.\n"
441 | SOME m =>
442 case m of
443 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been created.\n")
444 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
445 | _ => print "Unexpected server reply.\n";
446 OpenSSL.close bio
447 end
448
449fun requestListMailboxes domain =
450 let
451 val (_, bio) = requestBio (fn () => ())
452 in
453 Msg.send (bio, MsgListMailboxes domain);
454 (case Msg.recv bio of
455 NONE => Vmail.Error "Server closed connection unexpectedly."
456 | SOME m =>
457 case m of
458 MsgMailboxes users => (Msg.send (bio, MsgOk);
459 Vmail.Listing users)
460 | MsgError s => Vmail.Error ("Creation failed: " ^ s)
461 | _ => Vmail.Error "Unexpected server reply.")
462 before OpenSSL.close bio
463 end
464
465fun requestNewMailbox p =
466 let
467 val (_, bio) = requestBio (fn () => ())
468 in
469 Msg.send (bio, MsgNewMailbox p);
470 case Msg.recv bio of
471 NONE => print "Server closed connection unexpectedly.\n"
472 | SOME m =>
473 case m of
474 MsgOk => print ("A mapping for " ^ #user p ^ "@" ^ #domain p ^ " has been created.\n")
475 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
476 | _ => print "Unexpected server reply.\n";
477 OpenSSL.close bio
478 end
479
480fun requestPasswdMailbox p =
481 let
482 val (_, bio) = requestBio (fn () => ())
483 in
484 Msg.send (bio, MsgPasswdMailbox p);
485 case Msg.recv bio of
486 NONE => print "Server closed connection unexpectedly.\n"
487 | SOME m =>
488 case m of
489 MsgOk => print ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n")
490 | MsgError s => print ("Set failed: " ^ s ^ "\n")
491 | _ => print "Unexpected server reply.\n";
492 OpenSSL.close bio
493 end
494
495fun requestRmMailbox p =
496 let
497 val (_, bio) = requestBio (fn () => ())
498 in
499 Msg.send (bio, MsgRmMailbox p);
500 case Msg.recv bio of
501 NONE => print "Server closed connection unexpectedly.\n"
502 | SOME m =>
503 case m of
504 MsgOk => print ("The mapping for mailbox " ^ #user p ^ "@" ^ #domain p ^ " has been deleted.\n")
505 | MsgError s => print ("Remove failed: " ^ s ^ "\n")
506 | _ => print "Unexpected server reply.\n";
507 OpenSSL.close bio
508 end
509
510fun requestSaQuery addr =
511 let
512 val (_, bio) = requestBio (fn () => ())
513 in
514 Msg.send (bio, MsgSaQuery addr);
515 (case Msg.recv bio of
516 NONE => print "Server closed connection unexpectedly.\n"
517 | SOME m =>
518 case m of
519 MsgSaStatus b => (print ("SpamAssassin filtering for " ^ addr ^ " is "
520 ^ (if b then "ON" else "OFF") ^ ".\n");
521 Msg.send (bio, MsgOk))
522 | MsgError s => print ("Query failed: " ^ s ^ "\n")
523 | _ => print "Unexpected server reply.\n")
524 before OpenSSL.close bio
525 end
526
527fun requestSaSet p =
528 let
529 val (_, bio) = requestBio (fn () => ())
530 in
531 Msg.send (bio, MsgSaSet p);
532 case Msg.recv bio of
533 NONE => print "Server closed connection unexpectedly.\n"
534 | SOME m =>
535 case m of
536 MsgOk => print ("SpamAssassin filtering for " ^ #1 p ^ " is now "
537 ^ (if #2 p then "ON" else "OFF") ^ ".\n")
538 | MsgError s => print ("Set failed: " ^ s ^ "\n")
539 | _ => print "Unexpected server reply.\n";
540 OpenSSL.close bio
541 end
542
543fun requestSmtpLog domain =
544 let
545 val (_, bio) = requestBio (fn () => ())
546
547 val _ = Msg.send (bio, MsgSmtpLogReq domain)
548
549 fun loop () =
550 case Msg.recv bio of
551 NONE => print "Server closed connection unexpectedly.\n"
552 | SOME m =>
553 case m of
554 MsgOk => ()
555 | MsgSmtpLogRes line => (print line;
556 loop ())
557 | MsgError s => print ("Log search failed: " ^ s ^ "\n")
558 | _ => print "Unexpected server reply.\n"
559 in
560 loop ();
561 OpenSSL.close bio
562 end
563
564fun regenerate context =
565 let
566 val b = basis ()
567 val () = Tycheck.disallowExterns ()
568
569 val () = Domain.resetGlobal ()
570
571 fun contactNode (node, ip) =
572 if node = Config.defaultNode then
573 Domain.resetLocal ()
574 else let
575 val bio = OpenSSL.connect (context,
576 ip
577 ^ ":"
578 ^ Int.toString Config.slavePort)
579 in
580 Msg.send (bio, MsgRegenerate);
581 case Msg.recv bio of
582 NONE => print "Slave closed connection unexpectedly\n"
583 | SOME m =>
584 case m of
585 MsgOk => print ("Slave " ^ node ^ " pre-regeneration finished\n")
586 | MsgError s => print ("Slave " ^ node
587 ^ " returned error: " ^
588 s ^ "\n")
589 | _ => print ("Slave " ^ node
590 ^ " returned unexpected command\n");
591 OpenSSL.close bio
592 end
593
594 fun doUser user =
595 let
596 val _ = Domain.setUser user
597 val _ = ErrorMsg.reset ()
598
599 val dname = Config.domtoolDir user
600
601 val dir = Posix.FileSys.opendir dname
602
603 fun loop files =
604 case Posix.FileSys.readdir dir of
605 NONE => (Posix.FileSys.closedir dir;
606 files)
607 | SOME fname =>
608 if notTmp fname then
609 loop (OS.Path.joinDirFile {dir = dname,
610 file = fname}
611 :: files)
612 else
613 loop files
614
615 val files = loop []
616 val (_, files) = Order.order (SOME b) files
617 in
618 if !ErrorMsg.anyErrors then
619 print ("User " ^ user ^ "'s configuration has errors!\n")
620 else
621 app eval' files
622 end
623 handle IO.Io _ => ()
624 | OS.SysErr (s, _) => print ("System error processing user " ^ user ^ ": " ^ s ^ "\n")
625 in
626 app contactNode Config.nodeIps;
627 Env.pre ();
628 app doUser (Acl.users ());
629 Env.post ()
630 end
631
632fun rmuser user =
633 let
634 val doms = Acl.class {user = user, class = "domain"}
635 val doms = List.filter (fn dom =>
636 case Acl.whoHas {class = "domain", value = dom} of
637 [_] => true
638 | _ => false) (StringSet.listItems doms)
639 in
640 Acl.rmuser user;
641 Domain.rmdom doms
642 end
643
644fun service () =
645 let
646 val () = Acl.read Config.aclFile
647
648 val context = OpenSSL.context (Config.serverCert,
649 Config.serverKey,
650 Config.trustStore)
651 val _ = Domain.set_context context
652
653 val sock = OpenSSL.listen (context, Config.dispatcherPort)
654
655 fun loop () =
656 case OpenSSL.accept sock of
657 NONE => ()
658 | SOME bio =>
659 let
660 val user = OpenSSL.peerCN bio
661 val () = print ("\nConnection from " ^ user ^ " at " ^ Date.toString (Date.fromTimeUniv (Time.now ())) ^ "\n")
662 val () = Domain.setUser user
663
664 fun doIt f cleanup =
665 ((case f () of
666 (msgLocal, SOME msgRemote) =>
667 (print msgLocal;
668 print "\n";
669 Msg.send (bio, MsgError msgRemote))
670 | (msgLocal, NONE) =>
671 (print msgLocal;
672 print "\n";
673 Msg.send (bio, MsgOk)))
674 handle OpenSSL.OpenSSL _ =>
675 print "OpenSSL error\n"
676 | OS.SysErr (s, _) =>
677 (print "System error: ";
678 print s;
679 print "\n";
680 Msg.send (bio, MsgError ("System error: " ^ s))
681 handle OpenSSL.OpenSSL _ => ())
682 | Fail s =>
683 (print "Failure: ";
684 print s;
685 print "\n";
686 Msg.send (bio, MsgError ("Failure: " ^ s))
687 handle OpenSSL.OpenSSL _ => ())
688 | ErrorMsg.Error =>
689 (print "Compilation error\n";
690 Msg.send (bio, MsgError "Error during configuration evaluation")
691 handle OpenSSL.OpenSSL _ => ());
692 (cleanup ();
693 ignore (OpenSSL.readChar bio);
694 OpenSSL.close bio)
695 handle OpenSSL.OpenSSL _ => ();
696 loop ())
697
698 fun doConfig codes =
699 let
700 val _ = print "Configuration:\n"
701 val _ = app (fn s => (print s; print "\n")) codes
702 val _ = print "\n"
703
704 val outname = OS.FileSys.tmpName ()
705
706 fun doOne code =
707 let
708 val outf = TextIO.openOut outname
709 in
710 TextIO.output (outf, code);
711 TextIO.closeOut outf;
712 eval' outname
713 end
714 in
715 doIt (fn () => (Env.pre ();
716 app doOne codes;
717 Env.post ();
718 Msg.send (bio, MsgOk);
719 ("Configuration complete.", NONE)))
720 (fn () => OS.FileSys.remove outname)
721 end
722
723 fun checkAddr s =
724 case String.fields (fn ch => ch = #"@") s of
725 [user'] =>
726 if user = user' then
727 SOME (SetSA.User s)
728 else
729 NONE
730 | [user', domain] =>
731 if Domain.validEmailUser user' andalso Domain.yourDomain domain then
732 SOME (SetSA.Email s)
733 else
734 NONE
735 | _ => NONE
736
737 fun cmdLoop () =
738 case Msg.recv bio of
739 NONE => (OpenSSL.close bio
740 handle OpenSSL.OpenSSL _ => ();
741 loop ())
742 | SOME m =>
743 case m of
744 MsgConfig code => doConfig [code]
745 | MsgMultiConfig codes => doConfig codes
746
747 | MsgShutdown =>
748 if Acl.query {user = user, class = "priv", value = "shutdown"} then
749 print ("Domtool dispatcher shutting down at " ^ Date.toString (Date.fromTimeUniv (Time.now ())) ^ "\n")
750 else
751 (OpenSSL.close bio
752 handle OpenSSL.OpenSSL _ => ();
753 loop ())
754
755 | MsgGrant acl =>
756 doIt (fn () =>
757 if Acl.query {user = user, class = "priv", value = "all"} then
758 (Acl.grant acl;
759 Acl.write Config.aclFile;
760 ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".",
761 NONE))
762 else
763 ("Unauthorized user asked to grant a permission!",
764 SOME "Not authorized to grant privileges"))
765 (fn () => ())
766
767 | MsgRevoke acl =>
768 doIt (fn () =>
769 if Acl.query {user = user, class = "priv", value = "all"} then
770 (Acl.revoke acl;
771 Acl.write Config.aclFile;
772 ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".",
773 NONE))
774 else
775 ("Unauthorized user asked to revoke a permission!",
776 SOME "Not authorized to revoke privileges"))
777 (fn () => ())
778
779 | MsgListPerms user =>
780 doIt (fn () =>
781 (Msg.send (bio, MsgPerms (Acl.queryAll user));
782 ("Sent permission list for user " ^ user ^ ".",
783 NONE)))
784 (fn () => ())
785
786 | MsgWhoHas perm =>
787 doIt (fn () =>
788 (Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
789 ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".",
790 NONE)))
791 (fn () => ())
792
793 | MsgRmdom doms =>
794 doIt (fn () =>
795 if Acl.query {user = user, class = "priv", value = "all"}
796 orelse List.all (fn dom => Acl.query {user = user, class = "domain", value = dom}) doms then
797 (Domain.rmdom doms;
798 app (fn dom =>
799 Acl.revokeFromAll {class = "domain", value = dom}) doms;
800 Acl.write Config.aclFile;
801 ("Removed domains" ^ foldl (fn (d, s) => s ^ " " ^ d) "" doms ^ ".",
802 NONE))
803 else
804 ("Unauthorized user asked to remove a domain!",
805 SOME "Not authorized to remove that domain"))
806 (fn () => ())
807
808 | MsgRegenerate =>
809 doIt (fn () =>
810 if Acl.query {user = user, class = "priv", value = "regen"}
811 orelse Acl.query {user = user, class = "priv", value = "all"} then
812 (regenerate context;
813 ("Regenerated all configuration.",
814 NONE))
815 else
816 ("Unauthorized user asked to regenerate!",
817 SOME "Not authorized to regenerate"))
818 (fn () => ())
819
820 | MsgRmuser user' =>
821 doIt (fn () =>
822 if Acl.query {user = user, class = "priv", value = "all"} then
823 (rmuser user';
824 Acl.write Config.aclFile;
825 ("Removed user " ^ user' ^ ".",
826 NONE))
827 else
828 ("Unauthorized user asked to remove a user!",
829 SOME "Not authorized to remove users"))
830 (fn () => ())
831
832 | MsgCreateDbUser {dbtype, passwd} =>
833 doIt (fn () =>
834 case Dbms.lookup dbtype of
835 NONE => ("Database user creation request with unknown datatype type " ^ dbtype,
836 SOME ("Unknown database type " ^ dbtype))
837 | SOME handler =>
838 case #adduser handler {user = user, passwd = passwd} of
839 NONE => ("Added " ^ dbtype ^ " user " ^ user ^ ".",
840 NONE)
841 | SOME msg =>
842 ("Error adding a " ^ dbtype ^ " user " ^ user ^ ": " ^ msg,
843 SOME ("Error adding user: " ^ msg)))
844 (fn () => ())
845
846 | MsgDbPasswd {dbtype, passwd} =>
847 doIt (fn () =>
848 case Dbms.lookup dbtype of
849 NONE => ("Database passwd request with unknown datatype type " ^ dbtype,
850 SOME ("Unknown database type " ^ dbtype))
851 | SOME handler =>
852 case #passwd handler {user = user, passwd = passwd} of
853 NONE => ("Changed " ^ dbtype ^ " password of user " ^ user ^ ".",
854 NONE)
855 | SOME msg =>
856 ("Error setting " ^ dbtype ^ " password of user " ^ user ^ ": " ^ msg,
857 SOME ("Error adding user: " ^ msg)))
858 (fn () => ())
859
860 | MsgCreateDbTable {dbtype, dbname} =>
861 doIt (fn () =>
862 if Dbms.validDbname dbname then
863 case Dbms.lookup dbtype of
864 NONE => ("Database creation request with unknown datatype type " ^ dbtype,
865 SOME ("Unknown database type " ^ dbtype))
866 | SOME handler =>
867 case #createdb handler {user = user, dbname = dbname} of
868 NONE => ("Created database " ^ user ^ "_" ^ dbname ^ ".",
869 NONE)
870 | SOME msg => ("Error creating database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
871 SOME ("Error creating database: " ^ msg))
872 else
873 ("Invalid database name " ^ user ^ "_" ^ dbname,
874 SOME ("Invalid database name " ^ dbname)))
875 (fn () => ())
876
877 | MsgListMailboxes domain =>
878 doIt (fn () =>
879 if not (Domain.yourDomain domain) then
880 ("User wasn't authorized to list mailboxes for " ^ domain,
881 SOME "You're not authorized to configure that domain.")
882 else
883 case Vmail.list domain of
884 Vmail.Listing users => (Msg.send (bio, MsgMailboxes users);
885 ("Sent mailbox list for " ^ domain,
886 NONE))
887 | Vmail.Error msg => ("Error listing mailboxes for " ^ domain ^ ": " ^ msg,
888 SOME msg))
889 (fn () => ())
890
891 | MsgNewMailbox {domain, user = emailUser, passwd, mailbox} =>
892 doIt (fn () =>
893 if not (Domain.yourDomain domain) then
894 ("User wasn't authorized to add a mailbox to " ^ domain,
895 SOME "You're not authorized to configure that domain.")
896 else if not (Domain.validEmailUser emailUser) then
897 ("Invalid e-mail username " ^ emailUser,
898 SOME "Invalid e-mail username")
899 else if not (CharVector.all Char.isGraph passwd) then
900 ("Invalid password",
901 SOME "Invalid password; may only contain printable, non-space characters")
902 else if not (Domain.yourPath mailbox) then
903 ("User wasn't authorized to add a mailbox at " ^ mailbox,
904 SOME "You're not authorized to use that mailbox location.")
905 else
906 case Vmail.add {requester = user,
907 domain = domain, user = emailUser,
908 passwd = passwd, mailbox = mailbox} of
909 NONE => ("Added mailbox " ^ emailUser ^ "@" ^ domain ^ " at " ^ mailbox,
910 NONE)
911 | SOME msg => ("Error adding mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
912 SOME msg))
913 (fn () => ())
914
915 | MsgPasswdMailbox {domain, user = emailUser, passwd} =>
916 doIt (fn () =>
917 if not (Domain.yourDomain domain) then
918 ("User wasn't authorized to change password of a mailbox for " ^ domain,
919 SOME "You're not authorized to configure that domain.")
920 else if not (Domain.validEmailUser emailUser) then
921 ("Invalid e-mail username " ^ emailUser,
922 SOME "Invalid e-mail username")
923 else if not (CharVector.all Char.isGraph passwd) then
924 ("Invalid password",
925 SOME "Invalid password; may only contain printable, non-space characters")
926 else
927 case Vmail.passwd {domain = domain, user = emailUser,
928 passwd = passwd} of
929 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
930 NONE)
931 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
932 SOME msg))
933 (fn () => ())
934
935 | MsgRmMailbox {domain, user = emailUser} =>
936 doIt (fn () =>
937 if not (Domain.yourDomain domain) then
938 ("User wasn't authorized to change password of a mailbox for " ^ domain,
939 SOME "You're not authorized to configure that domain.")
940 else if not (Domain.validEmailUser emailUser) then
941 ("Invalid e-mail username " ^ emailUser,
942 SOME "Invalid e-mail username")
943 else
944 case Vmail.rm {domain = domain, user = emailUser} of
945 NONE => ("Deleted mailbox " ^ emailUser ^ "@" ^ domain,
946 NONE)
947 | SOME msg => ("Error deleting mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
948 SOME msg))
949 (fn () => ())
950
951 | MsgSaQuery addr =>
952 doIt (fn () =>
953 case checkAddr addr of
954 NONE => ("User tried to query SA filtering for " ^ addr,
955 SOME "You aren't allowed to configure SA filtering for that recipient.")
956 | SOME addr' => (Msg.send (bio, MsgSaStatus (SetSA.query addr'));
957 ("Queried SA filtering status for " ^ addr,
958 NONE)))
959 (fn () => ())
960
961 | MsgSaSet (addr, b) =>
962 doIt (fn () =>
963 case checkAddr addr of
964 NONE => ("User tried to set SA filtering for " ^ addr,
965 SOME "You aren't allowed to configure SA filtering for that recipient.")
966 | SOME addr' => (SetSA.set (addr', b);
967 Msg.send (bio, MsgOk);
968 ("Set SA filtering status for " ^ addr ^ " to "
969 ^ (if b then "ON" else "OFF"),
970 NONE)))
971 (fn () => ())
972
973 | MsgSmtpLogReq domain =>
974 doIt (fn () =>
975 if not (Domain.yourDomain domain) then
976 ("Unauthorized user tried to request SMTP logs for " ^ domain,
977 SOME "You aren't authorized to configure that domain.")
978 else
979 (SmtpLog.search (fn line => Msg.send (bio, MsgSmtpLogRes line))
980 domain;
981 ("Requested SMTP logs for " ^ domain,
982 NONE)))
983 (fn () => ())
984
985 | _ =>
986 doIt (fn () => ("Unexpected command",
987 SOME "Unexpected command"))
988 (fn () => ())
989 in
990 cmdLoop ()
991 end
992 handle OpenSSL.OpenSSL s =>
993 (print ("OpenSSL error: " ^ s ^ "\n");
994 OpenSSL.close bio
995 handle OpenSSL.OpenSSL _ => ();
996 loop ())
997 | OS.SysErr (s, _) =>
998 (print ("System error: " ^ s ^ "\n");
999 OpenSSL.close bio
1000 handle OpenSSL.OpenSSL _ => ();
1001 loop ())
1002 in
1003 print ("Domtool dispatcher starting up at " ^ Date.toString (Date.fromTimeUniv (Time.now ())) ^ "\n");
1004 print "Listening for connections....\n";
1005 loop ();
1006 OpenSSL.shutdown sock
1007 end
1008
1009fun slave () =
1010 let
1011 val host = Slave.hostname ()
1012
1013 val context = OpenSSL.context (Config.certDir ^ "/" ^ host ^ ".pem",
1014 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1015 Config.trustStore)
1016
1017 val sock = OpenSSL.listen (context, Config.slavePort)
1018
1019 fun loop () =
1020 case OpenSSL.accept sock of
1021 NONE => ()
1022 | SOME bio =>
1023 let
1024 val peer = OpenSSL.peerCN bio
1025 val () = print ("\nConnection from " ^ peer ^ "\n")
1026 in
1027 if peer <> Config.dispatcherName then
1028 (print "Not authorized!\n";
1029 OpenSSL.close bio;
1030 loop ())
1031 else let
1032 fun loop' files =
1033 case Msg.recv bio of
1034 NONE => print "Dispatcher closed connection unexpectedly\n"
1035 | SOME m =>
1036 case m of
1037 MsgFile file => loop' (file :: files)
1038 | MsgDoFiles => (Slave.handleChanges files;
1039 Msg.send (bio, MsgOk))
1040 | MsgRegenerate => (Domain.resetLocal ();
1041 Msg.send (bio, MsgOk))
1042 | _ => (print "Dispatcher sent unexpected command\n";
1043 Msg.send (bio, MsgError "Unexpected command"))
1044 in
1045 loop' [];
1046 ignore (OpenSSL.readChar bio);
1047 OpenSSL.close bio;
1048 loop ()
1049 end
1050 end handle OpenSSL.OpenSSL s =>
1051 (print ("OpenSSL error: "^ s ^ "\n");
1052 OpenSSL.close bio
1053 handle OpenSSL.OpenSSL _ => ();
1054 loop ())
1055 | OS.SysErr (s, _) =>
1056 (print ("System error: "^ s ^ "\n");
1057 OpenSSL.close bio
1058 handle OpenSSL.OpenSSL _ => ();
1059 loop ())
1060 in
1061 loop ();
1062 OpenSSL.shutdown sock
1063 end
1064
1065fun listBasis () =
1066 let
1067 val dir = Posix.FileSys.opendir Config.libRoot
1068
1069 fun loop files =
1070 case Posix.FileSys.readdir dir of
1071 NONE => (Posix.FileSys.closedir dir;
1072 files)
1073 | SOME fname =>
1074 if String.isSuffix ".dtl" fname then
1075 loop (OS.Path.joinDirFile {dir = Config.libRoot,
1076 file = fname}
1077 :: files)
1078 else
1079 loop files
1080 in
1081 loop []
1082 end
1083
1084fun autodocBasis outdir =
1085 Autodoc.autodoc {outdir = outdir, infiles = listBasis ()}
1086
1087end