Print on start of mysql-fixperms in domtool-server
[hcoop/domtool2.git] / src / main.sml
... / ...
CommitLineData
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006-2007, 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 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
41fun check' G fname =
42 let
43 val prog = Parse.parse fname
44 val prog = wrapFile (fname, prog)
45 in
46 if !ErrorMsg.anyErrors then
47 G
48 else
49 (if isLib fname then
50 ()
51 else
52 Option.app (Unused.check G) (#3 prog);
53 Tycheck.checkFile G (Defaults.tInit prog) prog)
54 end
55
56fun basis () =
57 let
58 val dir = Posix.FileSys.opendir Config.libRoot
59
60 fun loop files =
61 case Posix.FileSys.readdir dir of
62 NONE => (Posix.FileSys.closedir dir;
63 files)
64 | SOME fname =>
65 if String.isSuffix ".dtl" fname then
66 loop (OS.Path.joinDirFile {dir = Config.libRoot,
67 file = fname}
68 :: files)
69 else
70 loop files
71
72 val files = loop []
73 val (_, files) = Order.order NONE files
74 in
75 if !ErrorMsg.anyErrors then
76 Env.empty
77 else
78 (Tycheck.allowExterns ();
79 foldl (fn (fname, G) => check' G fname) Env.empty files
80 before Tycheck.disallowExterns ())
81 end
82
83(* val b = basis () *)
84
85fun check G fname =
86 let
87 val _ = ErrorMsg.reset ()
88 val _ = Env.preTycheck ()
89 in
90 if !ErrorMsg.anyErrors then
91 raise ErrorMsg.Error
92 else
93 let
94 val _ = Tycheck.disallowExterns ()
95 val _ = ErrorMsg.reset ()
96 val prog = Parse.parse fname
97 val prog = wrapFile (fname, prog)
98 in
99 if !ErrorMsg.anyErrors then
100 raise ErrorMsg.Error
101 else
102 let
103 val G' = Tycheck.checkFile G (Defaults.tInit prog) prog
104 in
105 if !ErrorMsg.anyErrors then
106 raise ErrorMsg.Error
107 else
108 (if isLib fname then
109 ()
110 else
111 Option.app (Unused.check G) (#3 prog);
112 (G', #3 prog))
113 end
114 end
115 end
116
117fun notTmp s =
118 String.sub (s, 0) <> #"."
119 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"_" orelse ch = #"-") s
120
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 =
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
160 raise ErrorMsg.Error
161 else
162 (foldl (fn (fname, G) => check' G fname) b files;
163 if !ErrorMsg.anyErrors then
164 raise ErrorMsg.Error
165 else
166 ())
167 end
168
169fun checkDir dname =
170 (setupUser ();
171 checkDir' dname)
172
173fun reduce G fname =
174 let
175 val (G, body) = check G fname
176 in
177 if !ErrorMsg.anyErrors then
178 (G, NONE)
179 else
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']))*)
189 (G, SOME body')
190 end
191 | _ => (G, NONE)
192 end
193
194(*(Defaults.eInit ())*)
195
196fun eval G evs fname =
197 case reduce G fname of
198 (G, SOME body') =>
199 if !ErrorMsg.anyErrors then
200 raise ErrorMsg.Error
201 else
202 let
203 val evs' = Eval.exec' evs body'
204 in
205 (G, evs')
206 end
207 | (G, NONE) => (G, evs)
208
209val dispatcher =
210 Config.dispatcher ^ ":" ^ Int.toString Config.dispatcherPort
211
212val self =
213 "localhost:" ^ Int.toString Config.slavePort
214
215fun context x =
216 (OpenSSL.context false x)
217 handle e as OpenSSL.OpenSSL s =>
218 (print "Couldn't find your certificate.\nYou probably haven't been given any Domtool privileges.\n";
219 print ("I looked in: " ^ #1 x ^ "\n");
220 print ("Additional information: " ^ s ^ "\n");
221 raise e)
222
223fun requestContext f =
224 let
225 val user = setupUser ()
226
227 val () = f ()
228
229 val context = context (Config.certDir ^ "/" ^ user ^ ".pem",
230 Config.keyDir ^ "/" ^ user ^ "/key.pem",
231 Config.trustStore)
232 in
233 (user, context)
234 end
235
236fun requestBio' printErr f =
237 let
238 val (user, context) = requestContext f
239 in
240 (user, OpenSSL.connect printErr (context, dispatcher))
241 end
242
243val requestBio = requestBio' true
244
245fun requestSlaveBio' printErr =
246 let
247 val (user, context) = requestContext (fn () => ())
248 in
249 (user, OpenSSL.connect printErr (context, self))
250 end
251
252fun requestSlaveBio () = requestSlaveBio' true
253
254fun request (fname, libOpt) =
255 let
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
269
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
278
279 val code = readFile fname
280 val msg = case libOpt of
281 NONE => MsgConfig code
282 | SOME fname' => MsgMultiConfig [readFile fname', code]
283 in
284 Msg.send (bio, msg);
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";
292 OpenSSL.close bio
293 end
294 handle ErrorMsg.Error => ()
295
296fun requestDir dname =
297 let
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";
303 print "files in your ~/.domtool directory?\n";
304 OS.Process.exit OS.Process.failure)
305
306 val _ = ErrorMsg.reset ()
307
308 val (user, bio) = requestBio (fn () => checkDir' dname)
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
330 (print "J\n";raise ErrorMsg.Error)
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
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)
359 end
360 handle ErrorMsg.Error => ()
361
362fun requestPing () =
363 let
364 val (_, bio) = requestBio' false (fn () => ())
365 in
366 OpenSSL.close bio;
367 OS.Process.success
368 end
369 handle _ => OS.Process.failure
370
371fun requestShutdown () =
372 let
373 val (_, bio) = requestBio (fn () => ())
374 in
375 Msg.send (bio, MsgShutdown);
376 case Msg.recv bio of
377 NONE => ()
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
386fun requestSlavePing () =
387 let
388 val (_, bio) = requestSlaveBio' false
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
401 NONE => ()
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
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
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
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
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
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
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
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
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
536fun requestDbUser dbtype =
537 let
538 val (_, bio) = requestBio (fn () => ())
539 in
540 Msg.send (bio, MsgCreateDbUser dbtype);
541 case Msg.recv bio of
542 NONE => print "Server closed connection unexpectedly.\n"
543 | SOME m =>
544 case m of
545 MsgOk => print "Your user has been created.\n"
546 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
547 | _ => print "Unexpected server reply.\n";
548 OpenSSL.close bio
549 end
550
551fun requestDbPasswd rc =
552 let
553 val (_, bio) = requestBio (fn () => ())
554 in
555 Msg.send (bio, MsgDbPasswd rc);
556 case Msg.recv bio of
557 NONE => print "Server closed connection unexpectedly.\n"
558 | SOME m =>
559 case m of
560 MsgOk => print "Your password has been changed.\n"
561 | MsgError s => print ("Password set failed: " ^ s ^ "\n")
562 | _ => print "Unexpected server reply.\n";
563 OpenSSL.close bio
564 end
565
566fun requestDbTable p =
567 let
568 val (user, bio) = requestBio (fn () => ())
569 in
570 Msg.send (bio, MsgCreateDb p);
571 case Msg.recv bio of
572 NONE => print "Server closed connection unexpectedly.\n"
573 | SOME m =>
574 case m of
575 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been created.\n")
576 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
577 | _ => print "Unexpected server reply.\n";
578 OpenSSL.close bio
579 end
580
581fun requestDbDrop p =
582 let
583 val (user, bio) = requestBio (fn () => ())
584 in
585 Msg.send (bio, MsgDropDb p);
586 case Msg.recv bio of
587 NONE => print "Server closed connection unexpectedly.\n"
588 | SOME m =>
589 case m of
590 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been dropped.\n")
591 | MsgError s => print ("Drop failed: " ^ s ^ "\n")
592 | _ => print "Unexpected server reply.\n";
593 OpenSSL.close bio
594 end
595
596fun requestDbGrant p =
597 let
598 val (user, bio) = requestBio (fn () => ())
599 in
600 Msg.send (bio, MsgGrantDb p);
601 case Msg.recv bio of
602 NONE => print "Server closed connection unexpectedly.\n"
603 | SOME m =>
604 case m of
605 MsgOk => print ("You've been granted all allowed privileges to database " ^ user ^ "_" ^ #dbname p ^ ".\n")
606 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
607 | _ => print "Unexpected server reply.\n";
608 OpenSSL.close bio
609 end
610
611fun requestListMailboxes domain =
612 let
613 val (_, bio) = requestBio (fn () => ())
614 in
615 Msg.send (bio, MsgListMailboxes domain);
616 (case Msg.recv bio of
617 NONE => Vmail.Error "Server closed connection unexpectedly."
618 | SOME m =>
619 case m of
620 MsgMailboxes users => (Msg.send (bio, MsgOk);
621 Vmail.Listing users)
622 | MsgError s => Vmail.Error ("Listing failed: " ^ s)
623 | _ => Vmail.Error "Unexpected server reply.")
624 before OpenSSL.close bio
625 end
626
627fun requestNewMailbox p =
628 let
629 val (_, bio) = requestBio (fn () => ())
630 in
631 Msg.send (bio, MsgNewMailbox p);
632 case Msg.recv bio of
633 NONE => print "Server closed connection unexpectedly.\n"
634 | SOME m =>
635 case m of
636 MsgOk => print ("A mapping for " ^ #user p ^ "@" ^ #domain p ^ " has been created.\n")
637 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
638 | _ => print "Unexpected server reply.\n";
639 OpenSSL.close bio
640 end
641
642fun requestPasswdMailbox p =
643 let
644 val (_, bio) = requestBio (fn () => ())
645 in
646 Msg.send (bio, MsgPasswdMailbox p);
647 case Msg.recv bio of
648 NONE => print "Server closed connection unexpectedly.\n"
649 | SOME m =>
650 case m of
651 MsgOk => print ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n")
652 | MsgError s => print ("Set failed: " ^ s ^ "\n")
653 | _ => print "Unexpected server reply.\n";
654 OpenSSL.close bio
655 end
656
657fun requestRmMailbox p =
658 let
659 val (_, bio) = requestBio (fn () => ())
660 in
661 Msg.send (bio, MsgRmMailbox p);
662 case Msg.recv bio of
663 NONE => print "Server closed connection unexpectedly.\n"
664 | SOME m =>
665 case m of
666 MsgOk => print ("The mapping for mailbox " ^ #user p ^ "@" ^ #domain p ^ " has been deleted.\n")
667 | MsgError s => print ("Remove failed: " ^ s ^ "\n")
668 | _ => print "Unexpected server reply.\n";
669 OpenSSL.close bio
670 end
671
672fun requestSaQuery addr =
673 let
674 val (_, bio) = requestBio (fn () => ())
675 in
676 Msg.send (bio, MsgSaQuery addr);
677 (case Msg.recv bio of
678 NONE => print "Server closed connection unexpectedly.\n"
679 | SOME m =>
680 case m of
681 MsgSaStatus b => (print ("SpamAssassin filtering for " ^ addr ^ " is "
682 ^ (if b then "ON" else "OFF") ^ ".\n");
683 Msg.send (bio, MsgOk))
684 | MsgError s => print ("Query failed: " ^ s ^ "\n")
685 | _ => print "Unexpected server reply.\n")
686 before OpenSSL.close bio
687 end
688
689fun requestSaSet p =
690 let
691 val (_, bio) = requestBio (fn () => ())
692 in
693 Msg.send (bio, MsgSaSet p);
694 case Msg.recv bio of
695 NONE => print "Server closed connection unexpectedly.\n"
696 | SOME m =>
697 case m of
698 MsgOk => print ("SpamAssassin filtering for " ^ #1 p ^ " is now "
699 ^ (if #2 p then "ON" else "OFF") ^ ".\n")
700 | MsgError s => print ("Set failed: " ^ s ^ "\n")
701 | _ => print "Unexpected server reply.\n";
702 OpenSSL.close bio
703 end
704
705fun requestSmtpLog domain =
706 let
707 val (_, bio) = requestBio (fn () => ())
708
709 val _ = Msg.send (bio, MsgSmtpLogReq domain)
710
711 fun loop () =
712 case Msg.recv bio of
713 NONE => print "Server closed connection unexpectedly.\n"
714 | SOME m =>
715 case m of
716 MsgOk => ()
717 | MsgSmtpLogRes line => (print line;
718 loop ())
719 | MsgError s => print ("Log search failed: " ^ s ^ "\n")
720 | _ => print "Unexpected server reply.\n"
721 in
722 loop ();
723 OpenSSL.close bio
724 end
725
726fun requestMysqlFixperms () =
727 let
728 val (_, bio) = requestBio (fn () => ())
729 in
730 Msg.send (bio, MsgMysqlFixperms);
731 case Msg.recv bio of
732 NONE => print "Server closed connection unexpectedly.\n"
733 | SOME m =>
734 case m of
735 MsgOk => print "Permissions granted.\n"
736 | MsgError s => print ("Failed: " ^ s ^ "\n")
737 | _ => print "Unexpected server reply.\n";
738 OpenSSL.close bio
739 end
740
741fun requestApt {node, pkg} =
742 let
743 val (user, context) = requestContext (fn () => ())
744 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
745 dispatcher
746 else
747 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
748
749 val _ = Msg.send (bio, MsgQuery (QApt pkg))
750
751 fun loop () =
752 case Msg.recv bio of
753 NONE => (print "Server closed connection unexpectedly.\n";
754 OS.Process.failure)
755 | SOME m =>
756 case m of
757 MsgYes => (print "Package is installed.\n";
758 OS.Process.success)
759 | MsgNo => (print "Package is not installed.\n";
760 OS.Process.failure)
761 | MsgError s => (print ("APT query failed: " ^ s ^ "\n");
762 OS.Process.failure)
763 | _ => (print "Unexpected server reply.\n";
764 OS.Process.failure)
765 in
766 loop ()
767 before OpenSSL.close bio
768 end
769
770fun requestCron {node, uname} =
771 let
772 val (user, context) = requestContext (fn () => ())
773 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
774 dispatcher
775 else
776 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
777
778 val _ = Msg.send (bio, MsgQuery (QCron uname))
779
780 fun loop () =
781 case Msg.recv bio of
782 NONE => (print "Server closed connection unexpectedly.\n";
783 OS.Process.failure)
784 | SOME m =>
785 case m of
786 MsgYes => (print "User has cron permissions.\n";
787 OS.Process.success)
788 | MsgNo => (print "User does not have cron permissions.\n";
789 OS.Process.failure)
790 | MsgError s => (print ("Cron query failed: " ^ s ^ "\n");
791 OS.Process.failure)
792 | _ => (print "Unexpected server reply.\n";
793 OS.Process.failure)
794 in
795 loop ()
796 before OpenSSL.close bio
797 end
798
799fun requestFtp {node, uname} =
800 let
801 val (user, context) = requestContext (fn () => ())
802 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
803 dispatcher
804 else
805 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
806
807 val _ = Msg.send (bio, MsgQuery (QFtp uname))
808
809 fun loop () =
810 case Msg.recv bio of
811 NONE => (print "Server closed connection unexpectedly.\n";
812 OS.Process.failure)
813 | SOME m =>
814 case m of
815 MsgYes => (print "User has FTP permissions.\n";
816 OS.Process.success)
817 | MsgNo => (print "User does not have FTP permissions.\n";
818 OS.Process.failure)
819 | MsgError s => (print ("FTP query failed: " ^ s ^ "\n");
820 OS.Process.failure)
821 | _ => (print "Unexpected server reply.\n";
822 OS.Process.failure)
823 in
824 loop ()
825 before OpenSSL.close bio
826 end
827
828fun requestTrustedPath {node, uname} =
829 let
830 val (user, context) = requestContext (fn () => ())
831 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
832 dispatcher
833 else
834 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
835
836 val _ = Msg.send (bio, MsgQuery (QTrustedPath uname))
837
838 fun loop () =
839 case Msg.recv bio of
840 NONE => (print "Server closed connection unexpectedly.\n";
841 OS.Process.failure)
842 | SOME m =>
843 case m of
844 MsgYes => (print "User has trusted path restriction.\n";
845 OS.Process.success)
846 | MsgNo => (print "User does not have trusted path restriction.\n";
847 OS.Process.failure)
848 | MsgError s => (print ("Trusted path query failed: " ^ s ^ "\n");
849 OS.Process.failure)
850 | _ => (print "Unexpected server reply.\n";
851 OS.Process.failure)
852 in
853 loop ()
854 before OpenSSL.close bio
855 end
856
857fun requestSocketPerm {node, uname} =
858 let
859 val (user, context) = requestContext (fn () => ())
860 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
861 dispatcher
862 else
863 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
864
865 val _ = Msg.send (bio, MsgQuery (QSocket uname))
866
867 fun loop () =
868 case Msg.recv bio of
869 NONE => (print "Server closed connection unexpectedly.\n";
870 OS.Process.failure)
871 | SOME m =>
872 case m of
873 MsgSocket p => (case p of
874 Any => print "Any\n"
875 | Client => print "Client\n"
876 | Server => print "Server\n"
877 | Nada => print "Nada\n";
878 OS.Process.success)
879 | MsgError s => (print ("Socket permission query failed: " ^ s ^ "\n");
880 OS.Process.failure)
881 | _ => (print "Unexpected server reply.\n";
882 OS.Process.failure)
883 in
884 loop ()
885 before OpenSSL.close bio
886 end
887
888fun requestFirewall {node, uname} =
889 let
890 val (user, context) = requestContext (fn () => ())
891 val bio = OpenSSL.connect true (context, if node = Config.masterNode then
892 dispatcher
893 else
894 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
895
896 val _ = Msg.send (bio, MsgQuery (QFirewall uname))
897
898 fun loop () =
899 case Msg.recv bio of
900 NONE => (print "Server closed connection unexpectedly.\n";
901 OS.Process.failure)
902 | SOME m =>
903 case m of
904 MsgFirewall ls => (app (fn s => (print s; print "\n")) ls;
905 OS.Process.success)
906 | MsgError s => (print ("Firewall query failed: " ^ s ^ "\n");
907 OS.Process.failure)
908 | _ => (print "Unexpected server reply.\n";
909 OS.Process.failure)
910 in
911 loop ()
912 before OpenSSL.close bio
913 end
914
915fun requestDescribe dom =
916 let
917 val (_, bio) = requestBio (fn () => ())
918 in
919 Msg.send (bio, MsgDescribe dom);
920 case Msg.recv bio of
921 NONE => print "Server closed connection unexpectedly.\n"
922 | SOME m =>
923 case m of
924 MsgDescription s => print s
925 | MsgError s => print ("Description failed: " ^ s ^ "\n")
926 | _ => print "Unexpected server reply.\n";
927 OpenSSL.close bio
928 end
929
930structure SS = StringSet
931
932fun domainList dname =
933 let
934 val dir = Posix.FileSys.opendir dname
935
936 fun visitNode dset =
937 case Posix.FileSys.readdir dir of
938 NONE => dset
939 | SOME node =>
940 let
941 val path = OS.Path.joinDirFile {dir = dname,
942 file = node}
943
944 fun visitDomains (path, bfor, dset) =
945 let
946 val dir = Posix.FileSys.opendir path
947
948 fun loop dset =
949 case Posix.FileSys.readdir dir of
950 NONE => dset
951 | SOME dname =>
952 let
953 val path = OS.Path.joinDirFile {dir = path,
954 file = dname}
955 in
956 if Posix.FileSys.ST.isDir (Posix.FileSys.stat path) then
957 let
958 val bfor = dname :: bfor
959 in
960 loop (visitDomains (path, bfor,
961 SS.add (dset,
962 String.concatWith "." bfor)))
963 end
964 else
965 loop dset
966 end
967 in
968 loop dset
969 before Posix.FileSys.closedir dir
970 end
971 in
972 visitNode (visitDomains (path, [], dset))
973 end
974 in
975 visitNode SS.empty
976 before Posix.FileSys.closedir dir
977 end
978
979fun regenerateEither tc checker context =
980 let
981 val () = print "Starting regeneration....\n"
982
983 val domainsBefore =
984 if tc then
985 SS.empty
986 else
987 domainList Config.resultRoot
988
989 fun ifReal f =
990 if tc then
991 ()
992 else
993 f ()
994
995 val _ = ErrorMsg.reset ()
996
997 val b = basis ()
998 val () = Tycheck.disallowExterns ()
999
1000 val () = ifReal (fn () =>
1001 (ignore (OS.Process.system ("rm -rf " ^ Config.oldResultRoot ^ "/*"));
1002 ignore (OS.Process.system ("cp -r " ^ Config.resultRoot
1003 ^ "/* " ^ Config.oldResultRoot ^ "/"));
1004 Domain.resetGlobal ()))
1005
1006 val ok = ref true
1007
1008 fun contactNode (node, ip) =
1009 if node = Config.defaultNode then
1010 Domain.resetLocal ()
1011 else let
1012 val bio = OpenSSL.connect true (context,
1013 ip
1014 ^ ":"
1015 ^ Int.toString Config.slavePort)
1016 in
1017 Msg.send (bio, MsgRegenerate);
1018 case Msg.recv bio of
1019 NONE => print "Slave closed connection unexpectedly\n"
1020 | SOME m =>
1021 case m of
1022 MsgOk => print ("Slave " ^ node ^ " pre-regeneration finished\n")
1023 | MsgError s => print ("Slave " ^ node
1024 ^ " returned error: " ^
1025 s ^ "\n")
1026 | _ => print ("Slave " ^ node
1027 ^ " returned unexpected command\n");
1028 OpenSSL.close bio
1029 end
1030 handle OpenSSL.OpenSSL s => print ("OpenSSL error: " ^ s ^ "\n")
1031
1032 fun doUser user =
1033 let
1034 val _ = Domain.setUser user
1035 val _ = ErrorMsg.reset ()
1036
1037 val dname = Config.domtoolDir user
1038 in
1039 if Posix.FileSys.access (dname, []) then
1040 let
1041 val dir = Posix.FileSys.opendir dname
1042
1043 fun loop files =
1044 case Posix.FileSys.readdir dir of
1045 NONE => (Posix.FileSys.closedir dir;
1046 files)
1047 | SOME fname =>
1048 if notTmp fname then
1049 loop (OS.Path.joinDirFile {dir = dname,
1050 file = fname}
1051 :: files)
1052 else
1053 loop files
1054
1055 val files = loop []
1056 val (_, files) = Order.order (SOME b) files
1057
1058 fun checker' (file, (G, evs)) =
1059 checker G evs file
1060 in
1061 if !ErrorMsg.anyErrors then
1062 (ErrorMsg.reset ();
1063 print ("User " ^ user ^ "'s configuration has errors!\n");
1064 ok := false)
1065 else
1066 ();
1067 ignore (foldl checker' (basis (), Defaults.eInit ()) files)
1068 end
1069 else if String.isSuffix "_admin" user then
1070 ()
1071 else
1072 (print ("Couldn't access " ^ user ^ "'s ~/.domtool directory.\n");
1073 ok := false)
1074 end
1075 handle IO.Io {name, function, ...} =>
1076 (print ("IO error processing user " ^ user ^ ": " ^ function ^ ": " ^ name ^ "\n");
1077 ok := false)
1078 | exn as OS.SysErr (s, _) => (print ("System error processing user " ^ user ^ ": " ^ s ^ "\n");
1079 ok := false)
1080 | ErrorMsg.Error => (ErrorMsg.reset ();
1081 print ("User " ^ user ^ " had a compilation error.\n");
1082 ok := false)
1083 | _ => (print "Unknown exception during regeneration!\n";
1084 ok := false)
1085 in
1086 ifReal (fn () => (app contactNode Config.nodeIps;
1087 Env.pre ()));
1088 app doUser (Acl.users ());
1089 ifReal (fn () =>
1090 let
1091 val domainsAfter = domainList Config.resultRoot
1092 val domainsGone = SS.difference (domainsBefore, domainsAfter)
1093 in
1094 if SS.isEmpty domainsGone then
1095 ()
1096 else
1097 (print "Domains to kill:";
1098 SS.app (fn s => (print " "; print s)) domainsGone;
1099 print "\n";
1100
1101 Domain.rmdom' Config.oldResultRoot (SS.listItems domainsGone));
1102
1103 Env.post ()
1104 end);
1105 !ok
1106 end
1107
1108val regenerate = regenerateEither false eval
1109val regenerateTc = regenerateEither true
1110 (fn G => fn evs => fn file =>
1111 (#1 (check G file), evs))
1112
1113fun rmuser user =
1114 let
1115 val doms = Acl.class {user = user, class = "domain"}
1116 val doms = List.filter (fn dom =>
1117 case Acl.whoHas {class = "domain", value = dom} of
1118 [_] => true
1119 | _ => false) (StringSet.listItems doms)
1120 in
1121 Acl.rmuser user;
1122 Domain.rmdom doms
1123 end
1124
1125fun now () = Date.toString (Date.fromTimeUniv (Time.now ()))
1126
1127fun answerQuery q =
1128 case q of
1129 QApt pkg => if Apt.installed pkg then MsgYes else MsgNo
1130 | QCron user => if Cron.allowed user then MsgYes else MsgNo
1131 | QFtp user => if Ftp.allowed user then MsgYes else MsgNo
1132 | QTrustedPath user => if TrustedPath.query user then MsgYes else MsgNo
1133 | QSocket user => MsgSocket (SocketPerm.query user)
1134 | QFirewall user => MsgFirewall (Firewall.query user)
1135
1136fun describeQuery q =
1137 case q of
1138 QApt pkg => "Requested installation status of package " ^ pkg
1139 | QCron user => "Asked about cron permissions for user " ^ user
1140 | QFtp user => "Asked about FTP permissions for user " ^ user
1141 | QTrustedPath user => "Asked about trusted path settings for user " ^ user
1142 | QSocket user => "Asked about socket permissions for user " ^ user
1143 | QFirewall user => "Asked about firewall rules for user " ^ user
1144
1145fun service () =
1146 let
1147 val host = Slave.hostname ()
1148
1149 val () = Acl.read Config.aclFile
1150
1151 val context = context (Config.certDir ^ "/" ^ host ^ ".pem",
1152 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1153 Config.trustStore)
1154 val _ = Domain.set_context context
1155
1156 val sock = OpenSSL.listen (context, Config.dispatcherPort)
1157
1158 fun loop () =
1159 (case OpenSSL.accept sock of
1160 NONE => ()
1161 | SOME bio =>
1162 let
1163 val user = OpenSSL.peerCN bio
1164 val () = print ("\nConnection from " ^ user ^ " at " ^ now () ^ "\n")
1165 val () = Domain.setUser user
1166
1167 fun doIt f cleanup =
1168 ((case f () of
1169 (msgLocal, SOME msgRemote) =>
1170 (print msgLocal;
1171 print "\n";
1172 Msg.send (bio, MsgError msgRemote))
1173 | (msgLocal, NONE) =>
1174 (print msgLocal;
1175 print "\n";
1176 Msg.send (bio, MsgOk)))
1177 handle e as (OpenSSL.OpenSSL s) =>
1178 (print ("OpenSSL error: " ^ s ^ "\n");
1179 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1180 Msg.send (bio, MsgError ("OpenSSL error: " ^ s))
1181 handle OpenSSL.OpenSSL _ => ())
1182 | OS.SysErr (s, _) =>
1183 (print "System error: ";
1184 print s;
1185 print "\n";
1186 Msg.send (bio, MsgError ("System error: " ^ s))
1187 handle OpenSSL.OpenSSL _ => ())
1188 | Fail s =>
1189 (print "Failure: ";
1190 print s;
1191 print "\n";
1192 Msg.send (bio, MsgError ("Failure: " ^ s))
1193 handle OpenSSL.OpenSSL _ => ())
1194 | ErrorMsg.Error =>
1195 (print "Compilation error\n";
1196 Msg.send (bio, MsgError "Error during configuration evaluation")
1197 handle OpenSSL.OpenSSL _ => ());
1198 (cleanup ();
1199 ignore (OpenSSL.readChar bio);
1200 OpenSSL.close bio)
1201 handle OpenSSL.OpenSSL _ => ();
1202 loop ())
1203
1204 fun doConfig codes =
1205 let
1206 val _ = print "Configuration:\n"
1207 val _ = app (fn s => (print s; print "\n")) codes
1208 val _ = print "\n"
1209
1210 val outname = OS.FileSys.tmpName ()
1211
1212 fun doOne (code, (G, evs)) =
1213 let
1214 val outf = TextIO.openOut outname
1215 in
1216 TextIO.output (outf, code);
1217 TextIO.closeOut outf;
1218 eval G evs outname
1219 end
1220 in
1221 doIt (fn () => (Env.pre ();
1222 ignore (foldl doOne (basis (), Defaults.eInit ()) codes);
1223 Env.post ();
1224 Msg.send (bio, MsgOk);
1225 ("Configuration complete.", NONE)))
1226 (fn () => OS.FileSys.remove outname)
1227 end
1228
1229 fun checkAddr s =
1230 case String.fields (fn ch => ch = #"@") s of
1231 [user'] =>
1232 if user = user' then
1233 SOME (SetSA.User s)
1234 else
1235 NONE
1236 | [user', domain] =>
1237 if Domain.validEmailUser user' andalso Domain.yourDomain domain then
1238 SOME (SetSA.Email s)
1239 else
1240 NONE
1241 | _ => NONE
1242
1243 fun cmdLoop () =
1244 case Msg.recv bio of
1245 NONE => (OpenSSL.close bio
1246 handle OpenSSL.OpenSSL _ => ();
1247 loop ())
1248 | SOME m =>
1249 case m of
1250 MsgConfig code => doConfig [code]
1251 | MsgMultiConfig codes => doConfig codes
1252
1253 | MsgShutdown =>
1254 if Acl.query {user = user, class = "priv", value = "all"}
1255 orelse Acl.query {user = user, class = "priv", value = "shutdown"} then
1256 print ("Domtool dispatcher shutting down at " ^ now () ^ "\n\n")
1257 else
1258 (print "Unauthorized shutdown command!\n";
1259 OpenSSL.close bio
1260 handle OpenSSL.OpenSSL _ => ();
1261 loop ())
1262
1263 | MsgGrant acl =>
1264 doIt (fn () =>
1265 if Acl.query {user = user, class = "priv", value = "all"} then
1266 (Acl.grant acl;
1267 Acl.write Config.aclFile;
1268 ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".",
1269 NONE))
1270 else
1271 ("Unauthorized user asked to grant a permission!",
1272 SOME "Not authorized to grant privileges"))
1273 (fn () => ())
1274
1275 | MsgRevoke acl =>
1276 doIt (fn () =>
1277 if Acl.query {user = user, class = "priv", value = "all"} then
1278 (Acl.revoke acl;
1279 Acl.write Config.aclFile;
1280 ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".",
1281 NONE))
1282 else
1283 ("Unauthorized user asked to revoke a permission!",
1284 SOME "Not authorized to revoke privileges"))
1285 (fn () => ())
1286
1287 | MsgListPerms user =>
1288 doIt (fn () =>
1289 (Msg.send (bio, MsgPerms (Acl.queryAll user));
1290 ("Sent permission list for user " ^ user ^ ".",
1291 NONE)))
1292 (fn () => ())
1293
1294 | MsgWhoHas perm =>
1295 doIt (fn () =>
1296 (Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
1297 ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".",
1298 NONE)))
1299 (fn () => ())
1300
1301 | MsgRmdom doms =>
1302 doIt (fn () =>
1303 if Acl.query {user = user, class = "priv", value = "all"}
1304 orelse List.all (fn dom => Domain.validDomain dom
1305 andalso Acl.queryDomain {user = user, domain = dom}) doms then
1306 (Domain.rmdom doms;
1307 (*app (fn dom =>
1308 Acl.revokeFromAll {class = "domain", value = dom}) doms;
1309 Acl.write Config.aclFile;*)
1310 ("Removed domains" ^ foldl (fn (d, s) => s ^ " " ^ d) "" doms ^ ".",
1311 NONE))
1312 else
1313 ("Unauthorized user asked to remove a domain!",
1314 SOME "Not authorized to remove that domain"))
1315 (fn () => ())
1316
1317 | MsgRegenerate =>
1318 doIt (fn () =>
1319 if Acl.query {user = user, class = "priv", value = "regen"}
1320 orelse Acl.query {user = user, class = "priv", value = "all"} then
1321 (if regenerate context then
1322 ("Regenerated all configuration.",
1323 NONE)
1324 else
1325 ("Error regenerating configuration!",
1326 SOME "Error regenerating configuration! Consult /var/log/domtool.log."))
1327 else
1328 ("Unauthorized user asked to regenerate!",
1329 SOME "Not authorized to regenerate"))
1330 (fn () => ())
1331
1332 | MsgRegenerateTc =>
1333 doIt (fn () =>
1334 if Acl.query {user = user, class = "priv", value = "regen"}
1335 orelse Acl.query {user = user, class = "priv", value = "all"} then
1336 (if regenerateTc context then
1337 ("Checked all configuration.",
1338 NONE)
1339 else
1340 ("Found a compilation error!",
1341 SOME "Found a compilation error! Consult /var/log/domtool.log."))
1342 else
1343 ("Unauthorized user asked to regenerate -tc!",
1344 SOME "Not authorized to regenerate -tc"))
1345 (fn () => ())
1346
1347 | MsgRmuser user' =>
1348 doIt (fn () =>
1349 if Acl.query {user = user, class = "priv", value = "all"} then
1350 (rmuser user';
1351 Acl.write Config.aclFile;
1352 ("Removed user " ^ user' ^ ".",
1353 NONE))
1354 else
1355 ("Unauthorized user asked to remove a user!",
1356 SOME "Not authorized to remove users"))
1357 (fn () => ())
1358
1359 | MsgCreateDbUser {dbtype, passwd} =>
1360 doIt (fn () =>
1361 case Dbms.lookup dbtype of
1362 NONE => ("Database user creation request with unknown datatype type " ^ dbtype,
1363 SOME ("Unknown database type " ^ dbtype))
1364 | SOME handler =>
1365 case #adduser handler {user = user, passwd = passwd} of
1366 NONE => ("Added " ^ dbtype ^ " user " ^ user ^ ".",
1367 NONE)
1368 | SOME msg =>
1369 ("Error adding a " ^ dbtype ^ " user " ^ user ^ ": " ^ msg,
1370 SOME ("Error adding user: " ^ msg)))
1371 (fn () => ())
1372
1373 | MsgDbPasswd {dbtype, passwd} =>
1374 doIt (fn () =>
1375 case Dbms.lookup dbtype of
1376 NONE => ("Database passwd request with unknown datatype type " ^ dbtype,
1377 SOME ("Unknown database type " ^ dbtype))
1378 | SOME handler =>
1379 case #passwd handler {user = user, passwd = passwd} of
1380 NONE => ("Changed " ^ dbtype ^ " password of user " ^ user ^ ".",
1381 NONE)
1382 | SOME msg =>
1383 ("Error setting " ^ dbtype ^ " password of user " ^ user ^ ": " ^ msg,
1384 SOME ("Error adding user: " ^ msg)))
1385 (fn () => ())
1386
1387 | MsgCreateDb {dbtype, dbname, encoding} =>
1388 doIt (fn () =>
1389 if Dbms.validDbname dbname then
1390 case Dbms.lookup dbtype of
1391 NONE => ("Database creation request with unknown datatype type " ^ dbtype,
1392 SOME ("Unknown database type " ^ dbtype))
1393 | SOME handler =>
1394 if not (Dbms.validEncoding encoding) then
1395 ("Invalid encoding " ^ valOf encoding ^ " requested for database creation.",
1396 SOME "Invalid encoding")
1397 else
1398 case #createdb handler {user = user, dbname = dbname, encoding = encoding} of
1399 NONE => ("Created database " ^ user ^ "_" ^ dbname ^ ".",
1400 NONE)
1401 | SOME msg => ("Error creating database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1402 SOME ("Error creating database: " ^ msg))
1403 else
1404 ("Invalid database name " ^ user ^ "_" ^ dbname,
1405 SOME ("Invalid database name " ^ dbname)))
1406 (fn () => ())
1407
1408 | MsgDropDb {dbtype, dbname} =>
1409 doIt (fn () =>
1410 if Dbms.validDbname dbname then
1411 case Dbms.lookup dbtype of
1412 NONE => ("Database drop request with unknown datatype type " ^ dbtype,
1413 SOME ("Unknown database type " ^ dbtype))
1414 | SOME handler =>
1415 case #dropdb handler {user = user, dbname = dbname} of
1416 NONE => ("Drop database " ^ user ^ "_" ^ dbname ^ ".",
1417 NONE)
1418 | SOME msg => ("Error dropping database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1419 SOME ("Error dropping database: " ^ msg))
1420 else
1421 ("Invalid database name " ^ user ^ "_" ^ dbname,
1422 SOME ("Invalid database name " ^ dbname)))
1423 (fn () => ())
1424
1425 | MsgGrantDb {dbtype, dbname} =>
1426 doIt (fn () =>
1427 if Dbms.validDbname dbname then
1428 case Dbms.lookup dbtype of
1429 NONE => ("Database drop request with unknown datatype type " ^ dbtype,
1430 SOME ("Unknown database type " ^ dbtype))
1431 | SOME handler =>
1432 case #grant handler {user = user, dbname = dbname} of
1433 NONE => ("Grant permissions to database " ^ user ^ "_" ^ dbname ^ ".",
1434 NONE)
1435 | SOME msg => ("Error granting permissions to database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1436 SOME ("Error granting permissions to database: " ^ msg))
1437 else
1438 ("Invalid database name " ^ user ^ "_" ^ dbname,
1439 SOME ("Invalid database name " ^ dbname)))
1440 (fn () => ())
1441
1442 | MsgListMailboxes domain =>
1443 doIt (fn () =>
1444 if not (Domain.yourDomain domain) then
1445 ("User wasn't authorized to list mailboxes for " ^ domain,
1446 SOME "You're not authorized to configure that domain.")
1447 else
1448 case Vmail.list domain of
1449 Vmail.Listing users => (Msg.send (bio, MsgMailboxes users);
1450 ("Sent mailbox list for " ^ domain,
1451 NONE))
1452 | Vmail.Error msg => ("Error listing mailboxes for " ^ domain ^ ": " ^ msg,
1453 SOME msg))
1454 (fn () => ())
1455
1456 | MsgNewMailbox {domain, user = emailUser, passwd, mailbox} =>
1457 doIt (fn () =>
1458 if not (Domain.yourDomain domain) then
1459 ("User wasn't authorized to add a mailbox to " ^ domain,
1460 SOME "You're not authorized to configure that domain.")
1461 else if not (Domain.validEmailUser emailUser) then
1462 ("Invalid e-mail username " ^ emailUser,
1463 SOME "Invalid e-mail username")
1464 else if not (CharVector.all Char.isGraph passwd) then
1465 ("Invalid password",
1466 SOME "Invalid password; may only contain printable, non-space characters")
1467 else if not (Domain.yourPath mailbox) then
1468 ("User wasn't authorized to add a mailbox at " ^ mailbox,
1469 SOME ("You're not authorized to use that mailbox location. ("
1470 ^ mailbox ^ ")"))
1471 else
1472 case Vmail.add {requester = user,
1473 domain = domain, user = emailUser,
1474 passwd = passwd, mailbox = mailbox} of
1475 NONE => ("Added mailbox " ^ emailUser ^ "@" ^ domain ^ " at " ^ mailbox,
1476 NONE)
1477 | SOME msg => ("Error adding mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1478 SOME msg))
1479 (fn () => ())
1480
1481 | MsgPasswdMailbox {domain, user = emailUser, passwd} =>
1482 doIt (fn () =>
1483 if not (Domain.yourDomain domain) then
1484 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1485 SOME "You're not authorized to configure that domain.")
1486 else if not (Domain.validEmailUser emailUser) then
1487 ("Invalid e-mail username " ^ emailUser,
1488 SOME "Invalid e-mail username")
1489 else if not (CharVector.all Char.isGraph passwd) then
1490 ("Invalid password",
1491 SOME "Invalid password; may only contain printable, non-space characters")
1492 else
1493 case Vmail.passwd {domain = domain, user = emailUser,
1494 passwd = passwd} of
1495 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
1496 NONE)
1497 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1498 SOME msg))
1499 (fn () => ())
1500
1501 | MsgRmMailbox {domain, user = emailUser} =>
1502 doIt (fn () =>
1503 if not (Domain.yourDomain domain) then
1504 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1505 SOME "You're not authorized to configure that domain.")
1506 else if not (Domain.validEmailUser emailUser) then
1507 ("Invalid e-mail username " ^ emailUser,
1508 SOME "Invalid e-mail username")
1509 else
1510 case Vmail.rm {domain = domain, user = emailUser} of
1511 NONE => ("Deleted mailbox " ^ emailUser ^ "@" ^ domain,
1512 NONE)
1513 | SOME msg => ("Error deleting mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
1514 SOME msg))
1515 (fn () => ())
1516
1517 | MsgSaQuery addr =>
1518 doIt (fn () =>
1519 case checkAddr addr of
1520 NONE => ("User tried to query SA filtering for " ^ addr,
1521 SOME "You aren't allowed to configure SA filtering for that recipient.")
1522 | SOME addr' => (Msg.send (bio, MsgSaStatus (SetSA.query addr'));
1523 ("Queried SA filtering status for " ^ addr,
1524 NONE)))
1525 (fn () => ())
1526
1527 | MsgSaSet (addr, b) =>
1528 doIt (fn () =>
1529 case checkAddr addr of
1530 NONE => ("User tried to set SA filtering for " ^ addr,
1531 SOME "You aren't allowed to configure SA filtering for that recipient.")
1532 | SOME addr' => (SetSA.set (addr', b);
1533 Msg.send (bio, MsgOk);
1534 ("Set SA filtering status for " ^ addr ^ " to "
1535 ^ (if b then "ON" else "OFF"),
1536 NONE)))
1537 (fn () => ())
1538
1539 | MsgSmtpLogReq domain =>
1540 doIt (fn () =>
1541 if not (Domain.yourDomain domain) then
1542 ("Unauthorized user tried to request SMTP logs for " ^ domain,
1543 SOME "You aren't authorized to configure that domain.")
1544 else
1545 (SmtpLog.search (fn line => Msg.send (bio, MsgSmtpLogRes line))
1546 domain;
1547 ("Requested SMTP logs for " ^ domain,
1548 NONE)))
1549 (fn () => ())
1550
1551 | MsgQuery q =>
1552 doIt (fn () => (Msg.send (bio, answerQuery q);
1553 (describeQuery q,
1554 NONE)))
1555 (fn () => ())
1556
1557 | MsgMysqlFixperms =>
1558 (print "Starting mysql-fixperms\n";
1559 doIt (fn () => if OS.Process.isSuccess
1560 (OS.Process.system "/usr/bin/sudo -H /afs/hcoop.net/common/etc/scripts/mysql-grant-table-drop") then
1561 ("Requested mysql-fixperms",
1562 NONE)
1563 else
1564 ("Requested mysql-fixperms, but execution failed!",
1565 SOME "Script execution failed."))
1566 (fn () => ()))
1567
1568 | MsgDescribe dom =>
1569 doIt (fn () => if not (Domain.validDomain dom) then
1570 ("Requested description of invalid domain " ^ dom,
1571 SOME "Invalid domain name")
1572 else if not (Domain.yourDomain dom
1573 orelse Acl.query {user = user, class = "priv", value = "all"}) then
1574 ("Requested description of " ^ dom ^ ", but not allowed access",
1575 SOME "Access denied")
1576 else
1577 (Msg.send (bio, MsgDescription (Domain.describe dom));
1578 ("Sent description of domain " ^ dom,
1579 NONE)))
1580 (fn () => ())
1581
1582 | _ =>
1583 doIt (fn () => ("Unexpected command",
1584 SOME "Unexpected command"))
1585 (fn () => ())
1586 in
1587 cmdLoop ()
1588 end
1589 handle e as (OpenSSL.OpenSSL s) =>
1590 (print ("OpenSSL error: " ^ s ^ "\n");
1591 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1592 OpenSSL.close bio
1593 handle OpenSSL.OpenSSL _ => ();
1594 loop ())
1595 | OS.SysErr (s, _) =>
1596 (print ("System error: " ^ s ^ "\n");
1597 OpenSSL.close bio
1598 handle OpenSSL.OpenSSL _ => ();
1599 loop ())
1600 | IO.Io {name, function, cause} =>
1601 (print ("IO error: " ^ function ^ " for " ^ name ^ "\n");
1602 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory cause);
1603 OpenSSL.close bio
1604 handle OpenSSL.OpenSSL _ => ();
1605 loop ())
1606 | OS.Path.InvalidArc =>
1607 (print "Invalid arc\n";
1608 OpenSSL.close bio
1609 handle OpenSSL.OpenSSL _ => ();
1610 loop ())
1611 | e =>
1612 (print "Unknown exception in main loop!\n";
1613 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1614 OpenSSL.close bio
1615 handle OpenSSL.OpenSSL _ => ();
1616 loop ()))
1617 handle e as (OpenSSL.OpenSSL s) =>
1618 (print ("OpenSSL error: " ^ s ^ "\n");
1619 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1620 loop ())
1621 | OS.SysErr (s, _) =>
1622 (print ("System error: " ^ s ^ "\n");
1623 loop ())
1624 | IO.Io {name, function, cause} =>
1625 (print ("IO error: " ^ function ^ " for " ^ name ^ "\n");
1626 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory cause);
1627 loop ())
1628 | e =>
1629 (print "Unknown exception in main loop!\n";
1630 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1631 loop ())
1632 in
1633 print ("Domtool dispatcher starting up at " ^ now () ^ "\n");
1634 print "Listening for connections....\n";
1635 loop ();
1636 OpenSSL.shutdown sock
1637 end
1638
1639fun slave () =
1640 let
1641 val host = Slave.hostname ()
1642
1643 val context = context (Config.certDir ^ "/" ^ host ^ ".pem",
1644 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1645 Config.trustStore)
1646
1647 val sock = OpenSSL.listen (context, Config.slavePort)
1648
1649 val _ = print ("Slave server starting at " ^ now () ^ "\n")
1650
1651 fun loop () =
1652 case OpenSSL.accept sock of
1653 NONE => ()
1654 | SOME bio =>
1655 let
1656 val peer = OpenSSL.peerCN bio
1657 val () = print ("\nConnection from " ^ peer ^ " at " ^ now () ^ "\n")
1658 in
1659 if peer = Config.dispatcherName then let
1660 fun loop' files =
1661 case Msg.recv bio of
1662 NONE => print "Dispatcher closed connection unexpectedly\n"
1663 | SOME m =>
1664 case m of
1665 MsgFile file => loop' (file :: files)
1666 | MsgDoFiles => (Slave.handleChanges files;
1667 Msg.send (bio, MsgOk))
1668 | MsgRegenerate => (Domain.resetLocal ();
1669 Msg.send (bio, MsgOk))
1670 | _ => (print "Dispatcher sent unexpected command\n";
1671 Msg.send (bio, MsgError "Unexpected command"))
1672 in
1673 loop' [];
1674 ignore (OpenSSL.readChar bio);
1675 OpenSSL.close bio;
1676 loop ()
1677 end
1678 else if peer = "domtool" then
1679 case Msg.recv bio of
1680 SOME MsgShutdown => (OpenSSL.close bio;
1681 print ("Shutting down at " ^ now () ^ "\n\n"))
1682 | _ => (OpenSSL.close bio;
1683 loop ())
1684 else
1685 case Msg.recv bio of
1686 SOME (MsgQuery q) => (print (describeQuery q ^ "\n");
1687 Msg.send (bio, answerQuery q);
1688 ignore (OpenSSL.readChar bio);
1689 OpenSSL.close bio;
1690 loop ())
1691 | _ => (OpenSSL.close bio;
1692 loop ())
1693 end handle OpenSSL.OpenSSL s =>
1694 (print ("OpenSSL error: " ^ s ^ "\n");
1695 OpenSSL.close bio
1696 handle OpenSSL.OpenSSL _ => ();
1697 loop ())
1698 | e as OS.SysErr (s, _) =>
1699 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1700 print ("System error: "^ s ^ "\n");
1701 OpenSSL.close bio
1702 handle OpenSSL.OpenSSL _ => ();
1703 loop ())
1704 | IO.Io {function, name, ...} =>
1705 (print ("IO error: " ^ function ^ ": " ^ name ^ "\n");
1706 OpenSSL.close bio
1707 handle OpenSSL.OpenSSL _ => ();
1708 loop ())
1709 | e =>
1710 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1711 print "Uncaught exception!\n";
1712 OpenSSL.close bio
1713 handle OpenSSL.OpenSSL _ => ();
1714 loop ())
1715 in
1716 loop ();
1717 OpenSSL.shutdown sock
1718 end
1719
1720fun listBasis () =
1721 let
1722 val dir = Posix.FileSys.opendir Config.libRoot
1723
1724 fun loop files =
1725 case Posix.FileSys.readdir dir of
1726 NONE => (Posix.FileSys.closedir dir;
1727 files)
1728 | SOME fname =>
1729 if String.isSuffix ".dtl" fname then
1730 loop (OS.Path.joinDirFile {dir = Config.libRoot,
1731 file = fname}
1732 :: files)
1733 else
1734 loop files
1735 in
1736 loop []
1737 end
1738
1739fun autodocBasis outdir =
1740 Autodoc.autodoc {outdir = outdir, infiles = listBasis ()}
1741
1742end