Apt package installation querying of dispatcher
[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
170val self =
171 "localhost:" ^ Int.toString Config.slavePort
172
173fun requestContext f =
174 let
175 val uid = Posix.ProcEnv.getuid ()
176 val user = Posix.SysDB.Passwd.name (Posix.SysDB.getpwuid uid)
177
178 val () = Acl.read Config.aclFile
179 val () = Domain.setUser user
180
181 val () = f ()
182
183 val context = OpenSSL.context (Config.certDir ^ "/" ^ user ^ ".pem",
184 Config.keyDir ^ "/" ^ user ^ "/key.pem",
185 Config.trustStore)
186 in
187 (user, context)
188 end
189
190fun requestBio f =
191 let
192 val (user, context) = requestContext f
193 in
194 (user, OpenSSL.connect (context, dispatcher))
195 end
196
197fun requestSlaveBio () =
198 let
199 val (user, context) = requestContext (fn () => ())
200 in
201 (user, OpenSSL.connect (context, self))
202 end
203
204fun request fname =
205 let
206 val (user, bio) = requestBio (fn () => ignore (check fname))
207
208 val inf = TextIO.openIn fname
209
210 fun loop lines =
211 case TextIO.inputLine inf of
212 NONE => String.concat (List.rev lines)
213 | SOME line => loop (line :: lines)
214
215 val code = loop []
216 in
217 TextIO.closeIn inf;
218 Msg.send (bio, MsgConfig code);
219 case Msg.recv bio of
220 NONE => print "Server closed connection unexpectedly.\n"
221 | SOME m =>
222 case m of
223 MsgOk => print "Configuration succeeded.\n"
224 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
225 | _ => print "Unexpected server reply.\n";
226 OpenSSL.close bio
227 end
228 handle ErrorMsg.Error => ()
229
230fun requestDir dname =
231 let
232 val _ = ErrorMsg.reset ()
233
234 val (user, bio) = requestBio (fn () => checkDir dname)
235
236 val b = basis ()
237
238 val dir = Posix.FileSys.opendir dname
239
240 fun loop files =
241 case Posix.FileSys.readdir dir of
242 NONE => (Posix.FileSys.closedir dir;
243 files)
244 | SOME fname =>
245 if notTmp fname then
246 loop (OS.Path.joinDirFile {dir = dname,
247 file = fname}
248 :: files)
249 else
250 loop files
251
252 val files = loop []
253 val (_, files) = Order.order (SOME b) files
254
255 val _ = if !ErrorMsg.anyErrors then
256 raise ErrorMsg.Error
257 else
258 ()
259
260 val codes = map (fn fname =>
261 let
262 val inf = TextIO.openIn fname
263
264 fun loop lines =
265 case TextIO.inputLine inf of
266 NONE => String.concat (rev lines)
267 | SOME line => loop (line :: lines)
268 in
269 loop []
270 before TextIO.closeIn inf
271 end) files
272 in
273 if !ErrorMsg.anyErrors then
274 ()
275 else
276 (Msg.send (bio, MsgMultiConfig codes);
277 case Msg.recv bio of
278 NONE => print "Server closed connection unexpectedly.\n"
279 | SOME m =>
280 case m of
281 MsgOk => print "Configuration succeeded.\n"
282 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
283 | _ => print "Unexpected server reply.\n";
284 OpenSSL.close bio)
285 end
286 handle ErrorMsg.Error => ()
287
288fun requestPing () =
289 let
290 val (_, bio) = requestBio (fn () => ())
291 in
292 OpenSSL.close bio;
293 OS.Process.success
294 end
295 handle _ => OS.Process.failure
296
297fun requestShutdown () =
298 let
299 val (_, bio) = requestBio (fn () => ())
300 in
301 Msg.send (bio, MsgShutdown);
302 case Msg.recv bio of
303 NONE => print "Server closed connection unexpectedly.\n"
304 | SOME m =>
305 case m of
306 MsgOk => print "Shutdown begun.\n"
307 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
308 | _ => print "Unexpected server reply.\n";
309 OpenSSL.close bio
310 end
311
312fun requestSlavePing () =
313 let
314 val (_, bio) = requestSlaveBio ()
315 in
316 OpenSSL.close bio;
317 OS.Process.success
318 end
319 handle _ => OS.Process.failure
320
321fun requestSlaveShutdown () =
322 let
323 val (_, bio) = requestSlaveBio ()
324 in
325 Msg.send (bio, MsgShutdown);
326 case Msg.recv bio of
327 NONE => print "Server closed connection unexpectedly.\n"
328 | SOME m =>
329 case m of
330 MsgOk => print "Shutdown begun.\n"
331 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
332 | _ => print "Unexpected server reply.\n";
333 OpenSSL.close bio
334 end
335
336fun requestGrant acl =
337 let
338 val (user, bio) = requestBio (fn () => ())
339 in
340 Msg.send (bio, MsgGrant acl);
341 case Msg.recv bio of
342 NONE => print "Server closed connection unexpectedly.\n"
343 | SOME m =>
344 case m of
345 MsgOk => print "Grant succeeded.\n"
346 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
347 | _ => print "Unexpected server reply.\n";
348 OpenSSL.close bio
349 end
350
351fun requestRevoke acl =
352 let
353 val (user, bio) = requestBio (fn () => ())
354 in
355 Msg.send (bio, MsgRevoke acl);
356 case Msg.recv bio of
357 NONE => print "Server closed connection unexpectedly.\n"
358 | SOME m =>
359 case m of
360 MsgOk => print "Revoke succeeded.\n"
361 | MsgError s => print ("Revoke failed: " ^ s ^ "\n")
362 | _ => print "Unexpected server reply.\n";
363 OpenSSL.close bio
364 end
365
366fun requestListPerms user =
367 let
368 val (_, bio) = requestBio (fn () => ())
369 in
370 Msg.send (bio, MsgListPerms user);
371 (case Msg.recv bio of
372 NONE => (print "Server closed connection unexpectedly.\n";
373 NONE)
374 | SOME m =>
375 case m of
376 MsgPerms perms => SOME perms
377 | MsgError s => (print ("Listing failed: " ^ s ^ "\n");
378 NONE)
379 | _ => (print "Unexpected server reply.\n";
380 NONE))
381 before OpenSSL.close bio
382 end
383
384fun requestWhoHas perm =
385 let
386 val (_, bio) = requestBio (fn () => ())
387 in
388 Msg.send (bio, MsgWhoHas perm);
389 (case Msg.recv bio of
390 NONE => (print "Server closed connection unexpectedly.\n";
391 NONE)
392 | SOME m =>
393 case m of
394 MsgWhoHasResponse users => SOME users
395 | MsgError s => (print ("whohas failed: " ^ s ^ "\n");
396 NONE)
397 | _ => (print "Unexpected server reply.\n";
398 NONE))
399 before OpenSSL.close bio
400 end
401
402fun requestRegen () =
403 let
404 val (_, bio) = requestBio (fn () => ())
405 in
406 Msg.send (bio, MsgRegenerate);
407 case Msg.recv bio of
408 NONE => print "Server closed connection unexpectedly.\n"
409 | SOME m =>
410 case m of
411 MsgOk => print "Regeneration succeeded.\n"
412 | MsgError s => print ("Regeneration failed: " ^ s ^ "\n")
413 | _ => print "Unexpected server reply.\n";
414 OpenSSL.close bio
415 end
416
417fun requestRmdom dom =
418 let
419 val (_, bio) = requestBio (fn () => ())
420 in
421 Msg.send (bio, MsgRmdom dom);
422 case Msg.recv bio of
423 NONE => print "Server closed connection unexpectedly.\n"
424 | SOME m =>
425 case m of
426 MsgOk => print "Removal succeeded.\n"
427 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
428 | _ => print "Unexpected server reply.\n";
429 OpenSSL.close bio
430 end
431
432fun requestRmuser user =
433 let
434 val (_, bio) = requestBio (fn () => ())
435 in
436 Msg.send (bio, MsgRmuser user);
437 case Msg.recv bio of
438 NONE => print "Server closed connection unexpectedly.\n"
439 | SOME m =>
440 case m of
441 MsgOk => print "Removal succeeded.\n"
442 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
443 | _ => print "Unexpected server reply.\n";
444 OpenSSL.close bio
445 end
446
447fun requestDbUser dbtype =
448 let
449 val (_, bio) = requestBio (fn () => ())
450 in
451 Msg.send (bio, MsgCreateDbUser dbtype);
452 case Msg.recv bio of
453 NONE => print "Server closed connection unexpectedly.\n"
454 | SOME m =>
455 case m of
456 MsgOk => print "Your user has been created.\n"
457 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
458 | _ => print "Unexpected server reply.\n";
459 OpenSSL.close bio
460 end
461
462fun requestDbPasswd rc =
463 let
464 val (_, bio) = requestBio (fn () => ())
465 in
466 Msg.send (bio, MsgDbPasswd rc);
467 case Msg.recv bio of
468 NONE => print "Server closed connection unexpectedly.\n"
469 | SOME m =>
470 case m of
471 MsgOk => print "Your password has been changed.\n"
472 | MsgError s => print ("Password set failed: " ^ s ^ "\n")
473 | _ => print "Unexpected server reply.\n";
474 OpenSSL.close bio
475 end
476
477fun requestDbTable p =
478 let
479 val (user, bio) = requestBio (fn () => ())
480 in
481 Msg.send (bio, MsgCreateDbTable p);
482 case Msg.recv bio of
483 NONE => print "Server closed connection unexpectedly.\n"
484 | SOME m =>
485 case m of
486 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been created.\n")
487 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
488 | _ => print "Unexpected server reply.\n";
489 OpenSSL.close bio
490 end
491
492fun requestListMailboxes domain =
493 let
494 val (_, bio) = requestBio (fn () => ())
495 in
496 Msg.send (bio, MsgListMailboxes domain);
497 (case Msg.recv bio of
498 NONE => Vmail.Error "Server closed connection unexpectedly."
499 | SOME m =>
500 case m of
501 MsgMailboxes users => (Msg.send (bio, MsgOk);
502 Vmail.Listing users)
503 | MsgError s => Vmail.Error ("Creation failed: " ^ s)
504 | _ => Vmail.Error "Unexpected server reply.")
505 before OpenSSL.close bio
506 end
507
508fun requestNewMailbox p =
509 let
510 val (_, bio) = requestBio (fn () => ())
511 in
512 Msg.send (bio, MsgNewMailbox p);
513 case Msg.recv bio of
514 NONE => print "Server closed connection unexpectedly.\n"
515 | SOME m =>
516 case m of
517 MsgOk => print ("A mapping for " ^ #user p ^ "@" ^ #domain p ^ " has been created.\n")
518 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
519 | _ => print "Unexpected server reply.\n";
520 OpenSSL.close bio
521 end
522
523fun requestPasswdMailbox p =
524 let
525 val (_, bio) = requestBio (fn () => ())
526 in
527 Msg.send (bio, MsgPasswdMailbox p);
528 case Msg.recv bio of
529 NONE => print "Server closed connection unexpectedly.\n"
530 | SOME m =>
531 case m of
532 MsgOk => print ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n")
533 | MsgError s => print ("Set failed: " ^ s ^ "\n")
534 | _ => print "Unexpected server reply.\n";
535 OpenSSL.close bio
536 end
537
538fun requestRmMailbox p =
539 let
540 val (_, bio) = requestBio (fn () => ())
541 in
542 Msg.send (bio, MsgRmMailbox p);
543 case Msg.recv bio of
544 NONE => print "Server closed connection unexpectedly.\n"
545 | SOME m =>
546 case m of
547 MsgOk => print ("The mapping for mailbox " ^ #user p ^ "@" ^ #domain p ^ " has been deleted.\n")
548 | MsgError s => print ("Remove failed: " ^ s ^ "\n")
549 | _ => print "Unexpected server reply.\n";
550 OpenSSL.close bio
551 end
552
553fun requestSaQuery addr =
554 let
555 val (_, bio) = requestBio (fn () => ())
556 in
557 Msg.send (bio, MsgSaQuery addr);
558 (case Msg.recv bio of
559 NONE => print "Server closed connection unexpectedly.\n"
560 | SOME m =>
561 case m of
562 MsgSaStatus b => (print ("SpamAssassin filtering for " ^ addr ^ " is "
563 ^ (if b then "ON" else "OFF") ^ ".\n");
564 Msg.send (bio, MsgOk))
565 | MsgError s => print ("Query failed: " ^ s ^ "\n")
566 | _ => print "Unexpected server reply.\n")
567 before OpenSSL.close bio
568 end
569
570fun requestSaSet p =
571 let
572 val (_, bio) = requestBio (fn () => ())
573 in
574 Msg.send (bio, MsgSaSet p);
575 case Msg.recv bio of
576 NONE => print "Server closed connection unexpectedly.\n"
577 | SOME m =>
578 case m of
579 MsgOk => print ("SpamAssassin filtering for " ^ #1 p ^ " is now "
580 ^ (if #2 p then "ON" else "OFF") ^ ".\n")
581 | MsgError s => print ("Set failed: " ^ s ^ "\n")
582 | _ => print "Unexpected server reply.\n";
583 OpenSSL.close bio
584 end
585
586fun requestSmtpLog domain =
587 let
588 val (_, bio) = requestBio (fn () => ())
589
590 val _ = Msg.send (bio, MsgSmtpLogReq domain)
591
592 fun loop () =
593 case Msg.recv bio of
594 NONE => print "Server closed connection unexpectedly.\n"
595 | SOME m =>
596 case m of
597 MsgOk => ()
598 | MsgSmtpLogRes line => (print line;
599 loop ())
600 | MsgError s => print ("Log search failed: " ^ s ^ "\n")
601 | _ => print "Unexpected server reply.\n"
602 in
603 loop ();
604 OpenSSL.close bio
605 end
606
607fun requestApt {node, pkg} =
608 let
609 val (_, bio) = requestBio (fn () => ())
610
611 val _ = Msg.send (bio, MsgApt pkg)
612
613 fun loop () =
614 case Msg.recv bio of
615 NONE => (print "Server closed connection unexpectedly.\n";
616 OS.Process.failure)
617 | SOME m =>
618 case m of
619 MsgYes => (print "Package is installed.\n";
620 OS.Process.success)
621 | MsgNo => (print "Package is not installed.\n";
622 OS.Process.failure)
623 | MsgError s => (print ("APT query failed: " ^ s ^ "\n");
624 OS.Process.failure)
625 | _ => (print "Unexpected server reply.\n";
626 OS.Process.failure)
627 in
628 loop ()
629 before OpenSSL.close bio
630 end
631
632fun regenerate context =
633 let
634 val b = basis ()
635 val () = Tycheck.disallowExterns ()
636
637 val () = Domain.resetGlobal ()
638
639 fun contactNode (node, ip) =
640 if node = Config.defaultNode then
641 Domain.resetLocal ()
642 else let
643 val bio = OpenSSL.connect (context,
644 ip
645 ^ ":"
646 ^ Int.toString Config.slavePort)
647 in
648 Msg.send (bio, MsgRegenerate);
649 case Msg.recv bio of
650 NONE => print "Slave closed connection unexpectedly\n"
651 | SOME m =>
652 case m of
653 MsgOk => print ("Slave " ^ node ^ " pre-regeneration finished\n")
654 | MsgError s => print ("Slave " ^ node
655 ^ " returned error: " ^
656 s ^ "\n")
657 | _ => print ("Slave " ^ node
658 ^ " returned unexpected command\n");
659 OpenSSL.close bio
660 end
661
662 fun doUser user =
663 let
664 val _ = Domain.setUser user
665 val _ = ErrorMsg.reset ()
666
667 val dname = Config.domtoolDir user
668
669 val dir = Posix.FileSys.opendir dname
670
671 fun loop files =
672 case Posix.FileSys.readdir dir of
673 NONE => (Posix.FileSys.closedir dir;
674 files)
675 | SOME fname =>
676 if notTmp fname then
677 loop (OS.Path.joinDirFile {dir = dname,
678 file = fname}
679 :: files)
680 else
681 loop files
682
683 val files = loop []
684 val (_, files) = Order.order (SOME b) files
685 in
686 if !ErrorMsg.anyErrors then
687 print ("User " ^ user ^ "'s configuration has errors!\n")
688 else
689 app eval' files
690 end
691 handle IO.Io _ => ()
692 | OS.SysErr (s, _) => print ("System error processing user " ^ user ^ ": " ^ s ^ "\n")
693 | ErrorMsg.Error => print ("User " ^ user ^ " had a compilation error.\n")
694 in
695 app contactNode Config.nodeIps;
696 Env.pre ();
697 app doUser (Acl.users ());
698 Env.post ()
699 end
700
701fun rmuser user =
702 let
703 val doms = Acl.class {user = user, class = "domain"}
704 val doms = List.filter (fn dom =>
705 case Acl.whoHas {class = "domain", value = dom} of
706 [_] => true
707 | _ => false) (StringSet.listItems doms)
708 in
709 Acl.rmuser user;
710 Domain.rmdom doms
711 end
712
713fun now () = Date.toString (Date.fromTimeUniv (Time.now ()))
714
715fun service () =
716 let
717 val () = Acl.read Config.aclFile
718
719 val context = OpenSSL.context (Config.serverCert,
720 Config.serverKey,
721 Config.trustStore)
722 val _ = Domain.set_context context
723
724 val sock = OpenSSL.listen (context, Config.dispatcherPort)
725
726 fun loop () =
727 case OpenSSL.accept sock of
728 NONE => ()
729 | SOME bio =>
730 let
731 val user = OpenSSL.peerCN bio
732 val () = print ("\nConnection from " ^ user ^ " at " ^ now () ^ "\n")
733 val () = Domain.setUser user
734
735 fun doIt f cleanup =
736 ((case f () of
737 (msgLocal, SOME msgRemote) =>
738 (print msgLocal;
739 print "\n";
740 Msg.send (bio, MsgError msgRemote))
741 | (msgLocal, NONE) =>
742 (print msgLocal;
743 print "\n";
744 Msg.send (bio, MsgOk)))
745 handle OpenSSL.OpenSSL _ =>
746 print "OpenSSL error\n"
747 | OS.SysErr (s, _) =>
748 (print "System error: ";
749 print s;
750 print "\n";
751 Msg.send (bio, MsgError ("System error: " ^ s))
752 handle OpenSSL.OpenSSL _ => ())
753 | Fail s =>
754 (print "Failure: ";
755 print s;
756 print "\n";
757 Msg.send (bio, MsgError ("Failure: " ^ s))
758 handle OpenSSL.OpenSSL _ => ())
759 | ErrorMsg.Error =>
760 (print "Compilation error\n";
761 Msg.send (bio, MsgError "Error during configuration evaluation")
762 handle OpenSSL.OpenSSL _ => ());
763 (cleanup ();
764 ignore (OpenSSL.readChar bio);
765 OpenSSL.close bio)
766 handle OpenSSL.OpenSSL _ => ();
767 loop ())
768
769 fun doConfig codes =
770 let
771 val _ = print "Configuration:\n"
772 val _ = app (fn s => (print s; print "\n")) codes
773 val _ = print "\n"
774
775 val outname = OS.FileSys.tmpName ()
776
777 fun doOne code =
778 let
779 val outf = TextIO.openOut outname
780 in
781 TextIO.output (outf, code);
782 TextIO.closeOut outf;
783 eval' outname
784 end
785 in
786 doIt (fn () => (Env.pre ();
787 app doOne codes;
788 Env.post ();
789 Msg.send (bio, MsgOk);
790 ("Configuration complete.", NONE)))
791 (fn () => OS.FileSys.remove outname)
792 end
793
794 fun checkAddr s =
795 case String.fields (fn ch => ch = #"@") s of
796 [user'] =>
797 if user = user' then
798 SOME (SetSA.User s)
799 else
800 NONE
801 | [user', domain] =>
802 if Domain.validEmailUser user' andalso Domain.yourDomain domain then
803 SOME (SetSA.Email s)
804 else
805 NONE
806 | _ => NONE
807
808 fun cmdLoop () =
809 case Msg.recv bio of
810 NONE => (OpenSSL.close bio
811 handle OpenSSL.OpenSSL _ => ();
812 loop ())
813 | SOME m =>
814 case m of
815 MsgConfig code => doConfig [code]
816 | MsgMultiConfig codes => doConfig codes
817
818 | MsgShutdown =>
819 if Acl.query {user = user, class = "priv", value = "all"}
820 orelse Acl.query {user = user, class = "priv", value = "shutdown"} then
821 print ("Domtool dispatcher shutting down at " ^ now () ^ "\n\n")
822 else
823 (print "Unauthorized shutdown command!\n";
824 OpenSSL.close bio
825 handle OpenSSL.OpenSSL _ => ();
826 loop ())
827
828 | MsgGrant acl =>
829 doIt (fn () =>
830 if Acl.query {user = user, class = "priv", value = "all"} then
831 (Acl.grant acl;
832 Acl.write Config.aclFile;
833 ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".",
834 NONE))
835 else
836 ("Unauthorized user asked to grant a permission!",
837 SOME "Not authorized to grant privileges"))
838 (fn () => ())
839
840 | MsgRevoke acl =>
841 doIt (fn () =>
842 if Acl.query {user = user, class = "priv", value = "all"} then
843 (Acl.revoke acl;
844 Acl.write Config.aclFile;
845 ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".",
846 NONE))
847 else
848 ("Unauthorized user asked to revoke a permission!",
849 SOME "Not authorized to revoke privileges"))
850 (fn () => ())
851
852 | MsgListPerms user =>
853 doIt (fn () =>
854 (Msg.send (bio, MsgPerms (Acl.queryAll user));
855 ("Sent permission list for user " ^ user ^ ".",
856 NONE)))
857 (fn () => ())
858
859 | MsgWhoHas perm =>
860 doIt (fn () =>
861 (Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
862 ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".",
863 NONE)))
864 (fn () => ())
865
866 | MsgRmdom doms =>
867 doIt (fn () =>
868 if Acl.query {user = user, class = "priv", value = "all"}
869 orelse List.all (fn dom => Acl.query {user = user, class = "domain", value = dom}) doms then
870 (Domain.rmdom doms;
871 app (fn dom =>
872 Acl.revokeFromAll {class = "domain", value = dom}) doms;
873 Acl.write Config.aclFile;
874 ("Removed domains" ^ foldl (fn (d, s) => s ^ " " ^ d) "" doms ^ ".",
875 NONE))
876 else
877 ("Unauthorized user asked to remove a domain!",
878 SOME "Not authorized to remove that domain"))
879 (fn () => ())
880
881 | MsgRegenerate =>
882 doIt (fn () =>
883 if Acl.query {user = user, class = "priv", value = "regen"}
884 orelse Acl.query {user = user, class = "priv", value = "all"} then
885 (regenerate context;
886 ("Regenerated all configuration.",
887 NONE))
888 else
889 ("Unauthorized user asked to regenerate!",
890 SOME "Not authorized to regenerate"))
891 (fn () => ())
892
893 | MsgRmuser user' =>
894 doIt (fn () =>
895 if Acl.query {user = user, class = "priv", value = "all"} then
896 (rmuser user';
897 Acl.write Config.aclFile;
898 ("Removed user " ^ user' ^ ".",
899 NONE))
900 else
901 ("Unauthorized user asked to remove a user!",
902 SOME "Not authorized to remove users"))
903 (fn () => ())
904
905 | MsgCreateDbUser {dbtype, passwd} =>
906 doIt (fn () =>
907 case Dbms.lookup dbtype of
908 NONE => ("Database user creation request with unknown datatype type " ^ dbtype,
909 SOME ("Unknown database type " ^ dbtype))
910 | SOME handler =>
911 case #adduser handler {user = user, passwd = passwd} of
912 NONE => ("Added " ^ dbtype ^ " user " ^ user ^ ".",
913 NONE)
914 | SOME msg =>
915 ("Error adding a " ^ dbtype ^ " user " ^ user ^ ": " ^ msg,
916 SOME ("Error adding user: " ^ msg)))
917 (fn () => ())
918
919 | MsgDbPasswd {dbtype, passwd} =>
920 doIt (fn () =>
921 case Dbms.lookup dbtype of
922 NONE => ("Database passwd request with unknown datatype type " ^ dbtype,
923 SOME ("Unknown database type " ^ dbtype))
924 | SOME handler =>
925 case #passwd handler {user = user, passwd = passwd} of
926 NONE => ("Changed " ^ dbtype ^ " password of user " ^ user ^ ".",
927 NONE)
928 | SOME msg =>
929 ("Error setting " ^ dbtype ^ " password of user " ^ user ^ ": " ^ msg,
930 SOME ("Error adding user: " ^ msg)))
931 (fn () => ())
932
933 | MsgCreateDbTable {dbtype, dbname} =>
934 doIt (fn () =>
935 if Dbms.validDbname dbname then
936 case Dbms.lookup dbtype of
937 NONE => ("Database creation request with unknown datatype type " ^ dbtype,
938 SOME ("Unknown database type " ^ dbtype))
939 | SOME handler =>
940 case #createdb handler {user = user, dbname = dbname} of
941 NONE => ("Created database " ^ user ^ "_" ^ dbname ^ ".",
942 NONE)
943 | SOME msg => ("Error creating database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
944 SOME ("Error creating database: " ^ msg))
945 else
946 ("Invalid database name " ^ user ^ "_" ^ dbname,
947 SOME ("Invalid database name " ^ dbname)))
948 (fn () => ())
949
950 | MsgListMailboxes domain =>
951 doIt (fn () =>
952 if not (Domain.yourDomain domain) then
953 ("User wasn't authorized to list mailboxes for " ^ domain,
954 SOME "You're not authorized to configure that domain.")
955 else
956 case Vmail.list domain of
957 Vmail.Listing users => (Msg.send (bio, MsgMailboxes users);
958 ("Sent mailbox list for " ^ domain,
959 NONE))
960 | Vmail.Error msg => ("Error listing mailboxes for " ^ domain ^ ": " ^ msg,
961 SOME msg))
962 (fn () => ())
963
964 | MsgNewMailbox {domain, user = emailUser, passwd, mailbox} =>
965 doIt (fn () =>
966 if not (Domain.yourDomain domain) then
967 ("User wasn't authorized to add a mailbox to " ^ domain,
968 SOME "You're not authorized to configure that domain.")
969 else if not (Domain.validEmailUser emailUser) then
970 ("Invalid e-mail username " ^ emailUser,
971 SOME "Invalid e-mail username")
972 else if not (CharVector.all Char.isGraph passwd) then
973 ("Invalid password",
974 SOME "Invalid password; may only contain printable, non-space characters")
975 else if not (Domain.yourPath mailbox) then
976 ("User wasn't authorized to add a mailbox at " ^ mailbox,
977 SOME "You're not authorized to use that mailbox location.")
978 else
979 case Vmail.add {requester = user,
980 domain = domain, user = emailUser,
981 passwd = passwd, mailbox = mailbox} of
982 NONE => ("Added mailbox " ^ emailUser ^ "@" ^ domain ^ " at " ^ mailbox,
983 NONE)
984 | SOME msg => ("Error adding mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
985 SOME msg))
986 (fn () => ())
987
988 | MsgPasswdMailbox {domain, user = emailUser, passwd} =>
989 doIt (fn () =>
990 if not (Domain.yourDomain domain) then
991 ("User wasn't authorized to change password of a mailbox for " ^ domain,
992 SOME "You're not authorized to configure that domain.")
993 else if not (Domain.validEmailUser emailUser) then
994 ("Invalid e-mail username " ^ emailUser,
995 SOME "Invalid e-mail username")
996 else if not (CharVector.all Char.isGraph passwd) then
997 ("Invalid password",
998 SOME "Invalid password; may only contain printable, non-space characters")
999 else
1000 case Vmail.passwd {domain = domain, user = emailUser,
1001 passwd = passwd} of
1002 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
1003 NONE)
1004 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1005 SOME msg))
1006 (fn () => ())
1007
1008 | MsgRmMailbox {domain, user = emailUser} =>
1009 doIt (fn () =>
1010 if not (Domain.yourDomain domain) then
1011 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1012 SOME "You're not authorized to configure that domain.")
1013 else if not (Domain.validEmailUser emailUser) then
1014 ("Invalid e-mail username " ^ emailUser,
1015 SOME "Invalid e-mail username")
1016 else
1017 case Vmail.rm {domain = domain, user = emailUser} of
1018 NONE => ("Deleted mailbox " ^ emailUser ^ "@" ^ domain,
1019 NONE)
1020 | SOME msg => ("Error deleting mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1021 SOME msg))
1022 (fn () => ())
1023
1024 | MsgSaQuery addr =>
1025 doIt (fn () =>
1026 case checkAddr addr of
1027 NONE => ("User tried to query SA filtering for " ^ addr,
1028 SOME "You aren't allowed to configure SA filtering for that recipient.")
1029 | SOME addr' => (Msg.send (bio, MsgSaStatus (SetSA.query addr'));
1030 ("Queried SA filtering status for " ^ addr,
1031 NONE)))
1032 (fn () => ())
1033
1034 | MsgSaSet (addr, b) =>
1035 doIt (fn () =>
1036 case checkAddr addr of
1037 NONE => ("User tried to set SA filtering for " ^ addr,
1038 SOME "You aren't allowed to configure SA filtering for that recipient.")
1039 | SOME addr' => (SetSA.set (addr', b);
1040 Msg.send (bio, MsgOk);
1041 ("Set SA filtering status for " ^ addr ^ " to "
1042 ^ (if b then "ON" else "OFF"),
1043 NONE)))
1044 (fn () => ())
1045
1046 | MsgSmtpLogReq domain =>
1047 doIt (fn () =>
1048 if not (Domain.yourDomain domain) then
1049 ("Unauthorized user tried to request SMTP logs for " ^ domain,
1050 SOME "You aren't authorized to configure that domain.")
1051 else
1052 (SmtpLog.search (fn line => Msg.send (bio, MsgSmtpLogRes line))
1053 domain;
1054 ("Requested SMTP logs for " ^ domain,
1055 NONE)))
1056 (fn () => ())
1057
1058 | MsgApt pkg =>
1059 doIt (fn () => (Msg.send (bio, if Apt.installed pkg then
1060 MsgYes
1061 else
1062 MsgNo);
1063 ("User requested installation status of package " ^ pkg,
1064 NONE)))
1065 (fn () => ())
1066
1067 | _ =>
1068 doIt (fn () => ("Unexpected command",
1069 SOME "Unexpected command"))
1070 (fn () => ())
1071 in
1072 cmdLoop ()
1073 end
1074 handle OpenSSL.OpenSSL s =>
1075 (print ("OpenSSL error: " ^ s ^ "\n");
1076 OpenSSL.close bio
1077 handle OpenSSL.OpenSSL _ => ();
1078 loop ())
1079 | OS.SysErr (s, _) =>
1080 (print ("System error: " ^ s ^ "\n");
1081 OpenSSL.close bio
1082 handle OpenSSL.OpenSSL _ => ();
1083 loop ())
1084 in
1085 print ("Domtool dispatcher starting up at " ^ now () ^ "\n");
1086 print "Listening for connections....\n";
1087 loop ();
1088 OpenSSL.shutdown sock
1089 end
1090
1091fun slave () =
1092 let
1093 val host = Slave.hostname ()
1094
1095 val context = OpenSSL.context (Config.certDir ^ "/" ^ host ^ ".pem",
1096 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1097 Config.trustStore)
1098
1099 val sock = OpenSSL.listen (context, Config.slavePort)
1100
1101 val _ = print ("Slave server starting at " ^ now () ^ "\n")
1102
1103 fun loop () =
1104 case OpenSSL.accept sock of
1105 NONE => ()
1106 | SOME bio =>
1107 let
1108 val peer = OpenSSL.peerCN bio
1109 val () = print ("\nConnection from " ^ peer ^ " at " ^ now () ^ "\n")
1110 in
1111 if peer = Config.dispatcherName then let
1112 fun loop' files =
1113 case Msg.recv bio of
1114 NONE => print "Dispatcher closed connection unexpectedly\n"
1115 | SOME m =>
1116 case m of
1117 MsgFile file => loop' (file :: files)
1118 | MsgDoFiles => (Slave.handleChanges files;
1119 Msg.send (bio, MsgOk))
1120 | MsgRegenerate => (Domain.resetLocal ();
1121 Msg.send (bio, MsgOk))
1122 | _ => (print "Dispatcher sent unexpected command\n";
1123 Msg.send (bio, MsgError "Unexpected command"))
1124 in
1125 loop' [];
1126 ignore (OpenSSL.readChar bio);
1127 OpenSSL.close bio;
1128 loop ()
1129 end
1130 else if peer = "domtool" then
1131 case Msg.recv bio of
1132 SOME MsgShutdown => (OpenSSL.close bio;
1133 print ("Shutting down at " ^ now () ^ "\n\n"))
1134 | _ => (OpenSSL.close bio;
1135 loop ())
1136 else
1137 (print "Not authorized!\n";
1138 OpenSSL.close bio;
1139 loop ())
1140 end handle OpenSSL.OpenSSL s =>
1141 (print ("OpenSSL error: "^ s ^ "\n");
1142 OpenSSL.close bio
1143 handle OpenSSL.OpenSSL _ => ();
1144 loop ())
1145 | OS.SysErr (s, _) =>
1146 (print ("System error: "^ s ^ "\n");
1147 OpenSSL.close bio
1148 handle OpenSSL.OpenSSL _ => ();
1149 loop ())
1150 in
1151 loop ();
1152 OpenSSL.shutdown sock
1153 end
1154
1155fun listBasis () =
1156 let
1157 val dir = Posix.FileSys.opendir Config.libRoot
1158
1159 fun loop files =
1160 case Posix.FileSys.readdir dir of
1161 NONE => (Posix.FileSys.closedir dir;
1162 files)
1163 | SOME fname =>
1164 if String.isSuffix ".dtl" fname then
1165 loop (OS.Path.joinDirFile {dir = Config.libRoot,
1166 file = fname}
1167 :: files)
1168 else
1169 loop files
1170 in
1171 loop []
1172 end
1173
1174fun autodocBasis outdir =
1175 Autodoc.autodoc {outdir = outdir, infiles = listBasis ()}
1176
1177end