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