domtool-admin regen -tc
[hcoop/domtool2.git] / src / main.sml
CommitLineData
234b917a 1(* HCoop Domtool (http://hcoop.sourceforge.net/)
f8ef6c20 2 * Copyright (c) 2006-2007, Adam Chlipala
234b917a
AC
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dac62e84 17 *)
234b917a
AC
18
19(* Main interface *)
20
21structure Main :> MAIN = struct
22
36e42cb8 23open Ast MsgTypes Print
234b917a 24
6ae327f8
AC
25structure SM = StringMap
26
aa56e112 27fun init () = Acl.read Config.aclFile
234b917a 28
d189ec0e 29fun check' G fname =
a3698041
AC
30 let
31 val prog = Parse.parse fname
32 in
33 if !ErrorMsg.anyErrors then
d189ec0e 34 G
a3698041 35 else
aa56e112 36 Tycheck.checkFile G (Defaults.tInit ()) prog
a3698041
AC
37 end
38
d189ec0e 39fun basis () =
234b917a 40 let
d189ec0e
AC
41 val dir = Posix.FileSys.opendir Config.libRoot
42
43 fun loop files =
44 case Posix.FileSys.readdir dir of
d612d62c
AC
45 NONE => (Posix.FileSys.closedir dir;
46 files)
d189ec0e
AC
47 | SOME fname =>
48 if String.isSuffix ".dtl" fname then
d612d62c
AC
49 loop (OS.Path.joinDirFile {dir = Config.libRoot,
50 file = fname}
d189ec0e
AC
51 :: files)
52 else
53 loop files
54
55 val files = loop []
c53e82e4 56 val (_, files) = Order.order NONE files
d189ec0e 57 in
6ae327f8
AC
58 if !ErrorMsg.anyErrors then
59 Env.empty
60 else
b3159a70
AC
61 (Tycheck.allowExterns ();
62 foldl (fn (fname, G) => check' G fname) Env.empty files
63 before Tycheck.disallowExterns ())
d189ec0e
AC
64 end
65
66fun check fname =
67 let
68 val _ = ErrorMsg.reset ()
12adf55a 69 val _ = Env.preTycheck ()
d189ec0e
AC
70
71 val b = basis ()
234b917a
AC
72 in
73 if !ErrorMsg.anyErrors then
36e42cb8 74 raise ErrorMsg.Error
234b917a
AC
75 else
76 let
b3159a70 77 val _ = Tycheck.disallowExterns ()
7f012ffd 78 val _ = ErrorMsg.reset ()
d189ec0e 79 val prog = Parse.parse fname
234b917a 80 in
492c1cff 81 if !ErrorMsg.anyErrors then
36e42cb8 82 raise ErrorMsg.Error
492c1cff 83 else
d189ec0e 84 let
aa56e112 85 val G' = Tycheck.checkFile b (Defaults.tInit ()) prog
d189ec0e 86 in
36e42cb8
AC
87 if !ErrorMsg.anyErrors then
88 raise ErrorMsg.Error
89 else
90 (G', #3 prog)
d189ec0e 91 end
234b917a
AC
92 end
93 end
94
c53e82e4
AC
95val notTmp = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"_" orelse ch = #"-")
96
97fun checkDir dname =
98 let
99 val b = basis ()
100
101 val dir = Posix.FileSys.opendir dname
102
103 fun loop files =
104 case Posix.FileSys.readdir dir of
105 NONE => (Posix.FileSys.closedir dir;
106 files)
107 | SOME fname =>
108 if notTmp fname then
109 loop (OS.Path.joinDirFile {dir = dname,
110 file = fname}
111 :: files)
112 else
113 loop files
114
115 val files = loop []
116 val (_, files) = Order.order (SOME b) files
117 in
118 if !ErrorMsg.anyErrors then
1824f573 119 raise ErrorMsg.Error
c53e82e4
AC
120 else
121 (foldl (fn (fname, G) => check' G fname) b files;
1824f573
AC
122 if !ErrorMsg.anyErrors then
123 raise ErrorMsg.Error
124 else
125 ())
c53e82e4
AC
126 end
127
d189ec0e 128fun reduce fname =
a3698041 129 let
d189ec0e 130 val (G, body) = check fname
a3698041
AC
131 in
132 if !ErrorMsg.anyErrors then
d189ec0e 133 NONE
a3698041 134 else
d189ec0e
AC
135 case body of
136 SOME body =>
137 let
138 val body' = Reduce.reduceExp G body
139 in
140 (*printd (PD.hovBox (PD.PPS.Rel 0,
141 [PD.string "Result:",
142 PD.space 1,
143 p_exp body']))*)
144 SOME body'
145 end
146 | _ => NONE
a3698041
AC
147 end
148
d189ec0e
AC
149fun eval fname =
150 case reduce fname of
151 (SOME body') =>
152 if !ErrorMsg.anyErrors then
36e42cb8 153 raise ErrorMsg.Error
d189ec0e 154 else
aa56e112 155 Eval.exec (Defaults.eInit ()) body'
36e42cb8 156 | NONE => raise ErrorMsg.Error
d189ec0e 157
1824f573
AC
158fun eval' fname =
159 case reduce fname of
160 (SOME body') =>
161 if !ErrorMsg.anyErrors then
162 raise ErrorMsg.Error
163 else
164 ignore (Eval.exec' (Defaults.eInit ()) body')
165 | NONE => raise ErrorMsg.Error
166
3b267643
AC
167val dispatcher =
168 Config.dispatcher ^ ":" ^ Int.toString Config.dispatcherPort
559e89e9 169
c9731b9b
AC
170val self =
171 "localhost:" ^ Int.toString Config.slavePort
172
d22c1f00
AC
173fun context x =
174 (OpenSSL.context false x)
25c93232 175 handle e as OpenSSL.OpenSSL s =>
d22c1f00 176 (print "Couldn't find your certificate.\nYou probably haven't been given any Domtool privileges.\n";
25c93232 177 print ("Additional information: " ^ s ^ "\n");
d22c1f00
AC
178 raise e)
179
e1b99e23 180fun setupUser () =
07cc384c 181 let
573a0ebc
AC
182 val user =
183 case Posix.ProcEnv.getenv "DOMTOOL_USER" of
184 NONE =>
185 let
186 val uid = Posix.ProcEnv.getuid ()
187 in
188 Posix.SysDB.Passwd.name (Posix.SysDB.getpwuid uid)
189 end
190 | SOME user => user
e1b99e23
AC
191 in
192 Acl.read Config.aclFile;
193 Domain.setUser user;
194 user
195 end
196
197fun requestContext f =
198 let
199 val user = setupUser ()
5ee41dd0
AC
200
201 val () = f ()
aa56e112 202
d22c1f00
AC
203 val context = context (Config.certDir ^ "/" ^ user ^ ".pem",
204 Config.keyDir ^ "/" ^ user ^ "/key.pem",
205 Config.trustStore)
5ee41dd0
AC
206 in
207 (user, context)
208 end
07cc384c 209
5ee41dd0
AC
210fun requestBio f =
211 let
212 val (user, context) = requestContext f
213 in
214 (user, OpenSSL.connect (context, dispatcher))
215 end
216
c9731b9b
AC
217fun requestSlaveBio () =
218 let
219 val (user, context) = requestContext (fn () => ())
220 in
221 (user, OpenSSL.connect (context, self))
222 end
223
5ee41dd0
AC
224fun request fname =
225 let
226 val (user, bio) = requestBio (fn () => ignore (check fname))
559e89e9 227
3b267643
AC
228 val inf = TextIO.openIn fname
229
36e42cb8 230 fun loop lines =
3b267643 231 case TextIO.inputLine inf of
36e42cb8
AC
232 NONE => String.concat (List.rev lines)
233 | SOME line => loop (line :: lines)
234
235 val code = loop []
559e89e9 236 in
3b267643 237 TextIO.closeIn inf;
36e42cb8
AC
238 Msg.send (bio, MsgConfig code);
239 case Msg.recv bio of
240 NONE => print "Server closed connection unexpectedly.\n"
241 | SOME m =>
242 case m of
243 MsgOk => print "Configuration succeeded.\n"
244 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
245 | _ => print "Unexpected server reply.\n";
3b267643 246 OpenSSL.close bio
559e89e9 247 end
aa56e112 248 handle ErrorMsg.Error => ()
559e89e9 249
c53e82e4
AC
250fun requestDir dname =
251 let
5982c377
AC
252 val _ = if Posix.FileSys.access (dname, []) then
253 ()
254 else
255 (print ("Can't access " ^ dname ^ ".\n");
256 print "Did you mean to run domtool on a specific file, instead of asking for all\n";
257 print "files in your ~/domtool directory?\n";
258 OS.Process.exit OS.Process.failure)
259
1824f573
AC
260 val _ = ErrorMsg.reset ()
261
262 val (user, bio) = requestBio (fn () => checkDir dname)
c53e82e4
AC
263
264 val b = basis ()
265
266 val dir = Posix.FileSys.opendir dname
267
268 fun loop files =
269 case Posix.FileSys.readdir dir of
270 NONE => (Posix.FileSys.closedir dir;
271 files)
272 | SOME fname =>
273 if notTmp fname then
274 loop (OS.Path.joinDirFile {dir = dname,
275 file = fname}
276 :: files)
277 else
278 loop files
279
280 val files = loop []
281 val (_, files) = Order.order (SOME b) files
282
283 val _ = if !ErrorMsg.anyErrors then
284 raise ErrorMsg.Error
285 else
286 ()
287
288 val codes = map (fn fname =>
289 let
290 val inf = TextIO.openIn fname
291
292 fun loop lines =
293 case TextIO.inputLine inf of
294 NONE => String.concat (rev lines)
295 | SOME line => loop (line :: lines)
296 in
297 loop []
298 before TextIO.closeIn inf
299 end) files
300 in
1824f573
AC
301 if !ErrorMsg.anyErrors then
302 ()
303 else
304 (Msg.send (bio, MsgMultiConfig codes);
305 case Msg.recv bio of
306 NONE => print "Server closed connection unexpectedly.\n"
307 | SOME m =>
308 case m of
309 MsgOk => print "Configuration succeeded.\n"
310 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
311 | _ => print "Unexpected server reply.\n";
312 OpenSSL.close bio)
c53e82e4
AC
313 end
314 handle ErrorMsg.Error => ()
315
62260c5f
AC
316fun requestPing () =
317 let
318 val (_, bio) = requestBio (fn () => ())
319 in
320 OpenSSL.close bio;
321 OS.Process.success
322 end
323 handle _ => OS.Process.failure
324
9f27d58f
AC
325fun requestShutdown () =
326 let
327 val (_, bio) = requestBio (fn () => ())
328 in
329 Msg.send (bio, MsgShutdown);
330 case Msg.recv bio of
331 NONE => print "Server closed connection unexpectedly.\n"
332 | SOME m =>
333 case m of
334 MsgOk => print "Shutdown begun.\n"
335 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
336 | _ => print "Unexpected server reply.\n";
337 OpenSSL.close bio
338 end
339
c9731b9b
AC
340fun requestSlavePing () =
341 let
342 val (_, bio) = requestSlaveBio ()
343 in
344 OpenSSL.close bio;
345 OS.Process.success
346 end
347 handle _ => OS.Process.failure
348
349fun requestSlaveShutdown () =
350 let
351 val (_, bio) = requestSlaveBio ()
352 in
353 Msg.send (bio, MsgShutdown);
354 case Msg.recv bio of
355 NONE => print "Server closed connection unexpectedly.\n"
356 | SOME m =>
357 case m of
358 MsgOk => print "Shutdown begun.\n"
359 | MsgError s => print ("Shutdown failed: " ^ s ^ "\n")
360 | _ => print "Unexpected server reply.\n";
361 OpenSSL.close bio
362 end
363
5ee41dd0
AC
364fun requestGrant acl =
365 let
366 val (user, bio) = requestBio (fn () => ())
367 in
368 Msg.send (bio, MsgGrant acl);
369 case Msg.recv bio of
370 NONE => print "Server closed connection unexpectedly.\n"
371 | SOME m =>
372 case m of
373 MsgOk => print "Grant succeeded.\n"
374 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
375 | _ => print "Unexpected server reply.\n";
376 OpenSSL.close bio
377 end
378
411a85f2
AC
379fun requestRevoke acl =
380 let
381 val (user, bio) = requestBio (fn () => ())
382 in
383 Msg.send (bio, MsgRevoke acl);
384 case Msg.recv bio of
385 NONE => print "Server closed connection unexpectedly.\n"
386 | SOME m =>
387 case m of
388 MsgOk => print "Revoke succeeded.\n"
389 | MsgError s => print ("Revoke failed: " ^ s ^ "\n")
390 | _ => print "Unexpected server reply.\n";
391 OpenSSL.close bio
392 end
393
08a04eb4
AC
394fun requestListPerms user =
395 let
396 val (_, bio) = requestBio (fn () => ())
397 in
398 Msg.send (bio, MsgListPerms user);
399 (case Msg.recv bio of
400 NONE => (print "Server closed connection unexpectedly.\n";
401 NONE)
402 | SOME m =>
403 case m of
404 MsgPerms perms => SOME perms
405 | MsgError s => (print ("Listing failed: " ^ s ^ "\n");
406 NONE)
407 | _ => (print "Unexpected server reply.\n";
408 NONE))
409 before OpenSSL.close bio
410 end
411
094877b1
AC
412fun requestWhoHas perm =
413 let
414 val (_, bio) = requestBio (fn () => ())
415 in
416 Msg.send (bio, MsgWhoHas perm);
417 (case Msg.recv bio of
418 NONE => (print "Server closed connection unexpectedly.\n";
419 NONE)
420 | SOME m =>
421 case m of
422 MsgWhoHasResponse users => SOME users
423 | MsgError s => (print ("whohas failed: " ^ s ^ "\n");
424 NONE)
425 | _ => (print "Unexpected server reply.\n";
426 NONE))
427 before OpenSSL.close bio
428 end
429
1824f573
AC
430fun requestRegen () =
431 let
432 val (_, bio) = requestBio (fn () => ())
433 in
434 Msg.send (bio, MsgRegenerate);
435 case Msg.recv bio of
436 NONE => print "Server closed connection unexpectedly.\n"
437 | SOME m =>
438 case m of
439 MsgOk => print "Regeneration succeeded.\n"
440 | MsgError s => print ("Regeneration failed: " ^ s ^ "\n")
441 | _ => print "Unexpected server reply.\n";
442 OpenSSL.close bio
443 end
444
fb6fac97
AC
445fun requestRegenTc () =
446 let
447 val (_, bio) = requestBio (fn () => ())
448 in
449 Msg.send (bio, MsgRegenerateTc);
450 case Msg.recv bio of
451 NONE => print "Server closed connection unexpectedly.\n"
452 | SOME m =>
453 case m of
454 MsgOk => print "All configuration validated.\n"
455 | MsgError s => print ("Configuration validation failed: " ^ s ^ "\n")
456 | _ => print "Unexpected server reply.\n";
457 OpenSSL.close bio
458 end
459
c189cbe9
AC
460fun requestRmdom dom =
461 let
462 val (_, bio) = requestBio (fn () => ())
463 in
464 Msg.send (bio, MsgRmdom dom);
465 case Msg.recv bio of
466 NONE => print "Server closed connection unexpectedly.\n"
467 | SOME m =>
468 case m of
469 MsgOk => print "Removal succeeded.\n"
470 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
471 | _ => print "Unexpected server reply.\n";
472 OpenSSL.close bio
473 end
474
e69e60cc
AC
475fun requestRmuser user =
476 let
477 val (_, bio) = requestBio (fn () => ())
478 in
479 Msg.send (bio, MsgRmuser user);
480 case Msg.recv bio of
481 NONE => print "Server closed connection unexpectedly.\n"
482 | SOME m =>
483 case m of
484 MsgOk => print "Removal succeeded.\n"
485 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
486 | _ => print "Unexpected server reply.\n";
487 OpenSSL.close bio
488 end
489
d541c618
AC
490fun requestDbUser dbtype =
491 let
492 val (_, bio) = requestBio (fn () => ())
493 in
494 Msg.send (bio, MsgCreateDbUser dbtype);
495 case Msg.recv bio of
496 NONE => print "Server closed connection unexpectedly.\n"
497 | SOME m =>
498 case m of
499 MsgOk => print "Your user has been created.\n"
500 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
501 | _ => print "Unexpected server reply.\n";
502 OpenSSL.close bio
503 end
504
86aa5de7
AC
505fun requestDbPasswd rc =
506 let
507 val (_, bio) = requestBio (fn () => ())
508 in
509 Msg.send (bio, MsgDbPasswd rc);
510 case Msg.recv bio of
511 NONE => print "Server closed connection unexpectedly.\n"
512 | SOME m =>
513 case m of
514 MsgOk => print "Your password has been changed.\n"
515 | MsgError s => print ("Password set failed: " ^ s ^ "\n")
516 | _ => print "Unexpected server reply.\n";
517 OpenSSL.close bio
518 end
519
90dd48df
AC
520fun requestDbTable p =
521 let
522 val (user, bio) = requestBio (fn () => ())
523 in
524 Msg.send (bio, MsgCreateDbTable p);
525 case Msg.recv bio of
526 NONE => print "Server closed connection unexpectedly.\n"
527 | SOME m =>
528 case m of
529 MsgOk => print ("Your database " ^ user ^ "_" ^ #dbname p ^ " has been created.\n")
530 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
531 | _ => print "Unexpected server reply.\n";
532 OpenSSL.close bio
533 end
534
1d3ef80e
AC
535fun requestListMailboxes domain =
536 let
537 val (_, bio) = requestBio (fn () => ())
538 in
539 Msg.send (bio, MsgListMailboxes domain);
540 (case Msg.recv bio of
2e96b9d4 541 NONE => Vmail.Error "Server closed connection unexpectedly."
1d3ef80e
AC
542 | SOME m =>
543 case m of
544 MsgMailboxes users => (Msg.send (bio, MsgOk);
545 Vmail.Listing users)
546 | MsgError s => Vmail.Error ("Creation failed: " ^ s)
2e96b9d4 547 | _ => Vmail.Error "Unexpected server reply.")
1d3ef80e
AC
548 before OpenSSL.close bio
549 end
550
08688401
AC
551fun requestNewMailbox p =
552 let
553 val (_, bio) = requestBio (fn () => ())
554 in
555 Msg.send (bio, MsgNewMailbox p);
556 case Msg.recv bio of
557 NONE => print "Server closed connection unexpectedly.\n"
558 | SOME m =>
559 case m of
560 MsgOk => print ("A mapping for " ^ #user p ^ "@" ^ #domain p ^ " has been created.\n")
561 | MsgError s => print ("Creation failed: " ^ s ^ "\n")
562 | _ => print "Unexpected server reply.\n";
563 OpenSSL.close bio
564 end
565
566fun requestPasswdMailbox p =
567 let
568 val (_, bio) = requestBio (fn () => ())
569 in
570 Msg.send (bio, MsgPasswdMailbox 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 ("The password for " ^ #user p ^ "@" ^ #domain p ^ " has been changed.\n")
576 | MsgError s => print ("Set failed: " ^ s ^ "\n")
577 | _ => print "Unexpected server reply.\n";
578 OpenSSL.close bio
579 end
580
581fun requestRmMailbox p =
582 let
583 val (_, bio) = requestBio (fn () => ())
584 in
585 Msg.send (bio, MsgRmMailbox 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 ("The mapping for mailbox " ^ #user p ^ "@" ^ #domain p ^ " has been deleted.\n")
591 | MsgError s => print ("Remove failed: " ^ s ^ "\n")
592 | _ => print "Unexpected server reply.\n";
593 OpenSSL.close bio
594 end
595
2e96b9d4
AC
596fun requestSaQuery addr =
597 let
598 val (_, bio) = requestBio (fn () => ())
599 in
600 Msg.send (bio, MsgSaQuery addr);
601 (case Msg.recv bio of
602 NONE => print "Server closed connection unexpectedly.\n"
603 | SOME m =>
604 case m of
605 MsgSaStatus b => (print ("SpamAssassin filtering for " ^ addr ^ " is "
606 ^ (if b then "ON" else "OFF") ^ ".\n");
607 Msg.send (bio, MsgOk))
608 | MsgError s => print ("Query failed: " ^ s ^ "\n")
609 | _ => print "Unexpected server reply.\n")
610 before OpenSSL.close bio
611 end
612
613fun requestSaSet p =
614 let
615 val (_, bio) = requestBio (fn () => ())
616 in
617 Msg.send (bio, MsgSaSet p);
618 case Msg.recv bio of
619 NONE => print "Server closed connection unexpectedly.\n"
620 | SOME m =>
621 case m of
622 MsgOk => print ("SpamAssassin filtering for " ^ #1 p ^ " is now "
623 ^ (if #2 p then "ON" else "OFF") ^ ".\n")
624 | MsgError s => print ("Set failed: " ^ s ^ "\n")
625 | _ => print "Unexpected server reply.\n";
626 OpenSSL.close bio
627 end
628
2bc5ed22
AC
629fun requestSmtpLog domain =
630 let
631 val (_, bio) = requestBio (fn () => ())
632
633 val _ = Msg.send (bio, MsgSmtpLogReq domain)
634
635 fun loop () =
636 case Msg.recv bio of
637 NONE => print "Server closed connection unexpectedly.\n"
638 | SOME m =>
639 case m of
640 MsgOk => ()
641 | MsgSmtpLogRes line => (print line;
642 loop ())
643 | MsgError s => print ("Log search failed: " ^ s ^ "\n")
644 | _ => print "Unexpected server reply.\n"
645 in
646 loop ();
647 OpenSSL.close bio
648 end
649
75585a67
AC
650fun requestApt {node, pkg} =
651 let
a95a0107
AC
652 val (user, context) = requestContext (fn () => ())
653 val bio = OpenSSL.connect (context, if node = Config.masterNode then
654 dispatcher
655 else
656 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
75585a67 657
a95a0107 658 val _ = Msg.send (bio, MsgQuery (QApt pkg))
75585a67
AC
659
660 fun loop () =
661 case Msg.recv bio of
662 NONE => (print "Server closed connection unexpectedly.\n";
663 OS.Process.failure)
664 | SOME m =>
665 case m of
666 MsgYes => (print "Package is installed.\n";
667 OS.Process.success)
668 | MsgNo => (print "Package is not installed.\n";
669 OS.Process.failure)
670 | MsgError s => (print ("APT query failed: " ^ s ^ "\n");
671 OS.Process.failure)
672 | _ => (print "Unexpected server reply.\n";
673 OS.Process.failure)
674 in
675 loop ()
676 before OpenSSL.close bio
677 end
678
d351d679
AC
679fun requestCron {node, uname} =
680 let
681 val (user, context) = requestContext (fn () => ())
682 val bio = OpenSSL.connect (context, if node = Config.masterNode then
683 dispatcher
684 else
685 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
686
687 val _ = Msg.send (bio, MsgQuery (QCron uname))
688
689 fun loop () =
690 case Msg.recv bio of
691 NONE => (print "Server closed connection unexpectedly.\n";
692 OS.Process.failure)
693 | SOME m =>
694 case m of
695 MsgYes => (print "User has cron permissions.\n";
696 OS.Process.success)
697 | MsgNo => (print "User does not have cron permissions.\n";
698 OS.Process.failure)
699 | MsgError s => (print ("Cron query failed: " ^ s ^ "\n");
700 OS.Process.failure)
701 | _ => (print "Unexpected server reply.\n";
702 OS.Process.failure)
703 in
704 loop ()
705 before OpenSSL.close bio
706 end
707
708fun requestFtp {node, uname} =
709 let
710 val (user, context) = requestContext (fn () => ())
711 val bio = OpenSSL.connect (context, if node = Config.masterNode then
712 dispatcher
713 else
714 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
715
716 val _ = Msg.send (bio, MsgQuery (QFtp uname))
717
718 fun loop () =
719 case Msg.recv bio of
720 NONE => (print "Server closed connection unexpectedly.\n";
721 OS.Process.failure)
722 | SOME m =>
723 case m of
724 MsgYes => (print "User has FTP permissions.\n";
725 OS.Process.success)
726 | MsgNo => (print "User does not have FTP permissions.\n";
727 OS.Process.failure)
728 | MsgError s => (print ("FTP query failed: " ^ s ^ "\n");
729 OS.Process.failure)
730 | _ => (print "Unexpected server reply.\n";
731 OS.Process.failure)
732 in
733 loop ()
734 before OpenSSL.close bio
735 end
736
4d5126e1
AC
737fun requestTrustedPath {node, uname} =
738 let
739 val (user, context) = requestContext (fn () => ())
740 val bio = OpenSSL.connect (context, if node = Config.masterNode then
741 dispatcher
742 else
743 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
744
745 val _ = Msg.send (bio, MsgQuery (QTrustedPath uname))
746
747 fun loop () =
748 case Msg.recv bio of
749 NONE => (print "Server closed connection unexpectedly.\n";
750 OS.Process.failure)
751 | SOME m =>
752 case m of
753 MsgYes => (print "User has trusted path restriction.\n";
754 OS.Process.success)
755 | MsgNo => (print "User does not have trusted path restriction.\n";
756 OS.Process.failure)
757 | MsgError s => (print ("Trusted path query failed: " ^ s ^ "\n");
758 OS.Process.failure)
759 | _ => (print "Unexpected server reply.\n";
760 OS.Process.failure)
761 in
762 loop ()
763 before OpenSSL.close bio
764 end
765
737c68d4
AC
766fun requestSocketPerm {node, uname} =
767 let
768 val (user, context) = requestContext (fn () => ())
769 val bio = OpenSSL.connect (context, if node = Config.masterNode then
770 dispatcher
771 else
772 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
773
774 val _ = Msg.send (bio, MsgQuery (QSocket uname))
775
776 fun loop () =
777 case Msg.recv bio of
778 NONE => (print "Server closed connection unexpectedly.\n";
779 OS.Process.failure)
780 | SOME m =>
781 case m of
782 MsgSocket p => (case p of
783 Any => print "Any\n"
784 | Client => print "Client\n"
785 | Server => print "Server\n"
786 | Nada => print "Nada\n";
787 OS.Process.success)
788 | MsgError s => (print ("Socket permission query failed: " ^ s ^ "\n");
789 OS.Process.failure)
790 | _ => (print "Unexpected server reply.\n";
791 OS.Process.failure)
792 in
793 loop ()
794 before OpenSSL.close bio
795 end
796
f9548f16
AC
797fun requestFirewall {node, uname} =
798 let
799 val (user, context) = requestContext (fn () => ())
800 val bio = OpenSSL.connect (context, if node = Config.masterNode then
801 dispatcher
802 else
803 Domain.nodeIp node ^ ":" ^ Int.toString Config.slavePort)
804
805 val _ = Msg.send (bio, MsgQuery (QFirewall uname))
806
807 fun loop () =
808 case Msg.recv bio of
809 NONE => (print "Server closed connection unexpectedly.\n";
810 OS.Process.failure)
811 | SOME m =>
812 case m of
813 MsgFirewall ls => (app (fn s => (print s; print "\n")) ls;
814 OS.Process.success)
815 | MsgError s => (print ("Firewall query failed: " ^ s ^ "\n");
816 OS.Process.failure)
817 | _ => (print "Unexpected server reply.\n";
818 OS.Process.failure)
819 in
820 loop ()
821 before OpenSSL.close bio
822 end
823
71420f8b 824fun regenerate context =
1824f573 825 let
6f3525e4
AC
826 val _ = ErrorMsg.reset ()
827
1824f573 828 val b = basis ()
71420f8b
AC
829 val () = Tycheck.disallowExterns ()
830
831 val () = Domain.resetGlobal ()
832
fb6fac97
AC
833 val ok = ref true
834
71420f8b
AC
835 fun contactNode (node, ip) =
836 if node = Config.defaultNode then
837 Domain.resetLocal ()
838 else let
839 val bio = OpenSSL.connect (context,
840 ip
841 ^ ":"
842 ^ Int.toString Config.slavePort)
843 in
844 Msg.send (bio, MsgRegenerate);
845 case Msg.recv bio of
846 NONE => print "Slave closed connection unexpectedly\n"
847 | SOME m =>
848 case m of
849 MsgOk => print ("Slave " ^ node ^ " pre-regeneration finished\n")
850 | MsgError s => print ("Slave " ^ node
851 ^ " returned error: " ^
852 s ^ "\n")
853 | _ => print ("Slave " ^ node
854 ^ " returned unexpected command\n");
855 OpenSSL.close bio
16465a9a
AC
856 end
857 handle OpenSSL.OpenSSL s => print ("OpenSSL error: " ^ s ^ "\n")
1824f573
AC
858
859 fun doUser user =
860 let
861 val _ = Domain.setUser user
862 val _ = ErrorMsg.reset ()
863
864 val dname = Config.domtoolDir user
fb6fac97
AC
865 in
866 if Posix.FileSys.access (dname, []) then
867 let
868 val dir = Posix.FileSys.opendir dname
869
870 fun loop files =
871 case Posix.FileSys.readdir dir of
872 NONE => (Posix.FileSys.closedir dir;
873 files)
874 | SOME fname =>
875 if notTmp fname then
876 loop (OS.Path.joinDirFile {dir = dname,
877 file = fname}
878 :: files)
879 else
880 loop files
1824f573 881
fb6fac97
AC
882 val files = loop []
883 val (_, files) = Order.order (SOME b) files
884 in
885 if !ErrorMsg.anyErrors then
886 (ErrorMsg.reset ();
887 print ("User " ^ user ^ "'s configuration has errors!\n"))
1824f573 888 else
fb6fac97
AC
889 app eval' files
890 end
1824f573 891 else
fb6fac97 892 ()
1824f573 893 end
fb6fac97
AC
894 handle IO.Io _ => ()
895 | OS.SysErr (s, _) => (print ("System error processing user " ^ user ^ ": " ^ s ^ "\n");
896 ok := false)
897 | ErrorMsg.Error => (ErrorMsg.reset ();
898 print ("User " ^ user ^ " had a compilation error.\n");
899 ok := false)
900 | _ => (print "Unknown exception during regeneration!\n";
901 ok := false)
1824f573 902 in
71420f8b 903 app contactNode Config.nodeIps;
1824f573
AC
904 Env.pre ();
905 app doUser (Acl.users ());
fb6fac97
AC
906 Env.post ();
907 !ok
908 end
909
910fun regenerateTc context =
911 let
912 val _ = ErrorMsg.reset ()
913
914 val b = basis ()
915 val () = Tycheck.disallowExterns ()
916
917 val () = Domain.resetGlobal ()
918
919 val ok = ref true
920
921 fun doUser user =
922 let
923 val _ = Domain.setUser user
924 val _ = ErrorMsg.reset ()
925
926 val dname = Config.domtoolDir user
927 in
928 if Posix.FileSys.access (dname, []) then
929 let
930 val dir = Posix.FileSys.opendir dname
931
932 fun loop files =
933 case Posix.FileSys.readdir dir of
934 NONE => (Posix.FileSys.closedir dir;
935 files)
936 | SOME fname =>
937 if notTmp fname then
938 loop (OS.Path.joinDirFile {dir = dname,
939 file = fname}
940 :: files)
941 else
942 loop files
943
944 val files = loop []
945 val (_, files) = Order.order (SOME b) files
946 in
947 if !ErrorMsg.anyErrors then
948 (ErrorMsg.reset ();
949 print ("User " ^ user ^ "'s configuration has errors!\n");
950 ok := false)
951 else
952 app (ignore o check) files
953 end
954 else
955 ()
956 end
957 handle IO.Io _ => ()
958 | OS.SysErr (s, _) => print ("System error processing user " ^ user ^ ": " ^ s ^ "\n")
959 | ErrorMsg.Error => (ErrorMsg.reset ();
960 print ("User " ^ user ^ " had a compilation error.\n"))
961 | _ => print "Unknown exception during -tc regeneration!\n"
962 in
963 app doUser (Acl.users ());
964 !ok
1824f573
AC
965 end
966
e69e60cc
AC
967fun rmuser user =
968 let
969 val doms = Acl.class {user = user, class = "domain"}
970 val doms = List.filter (fn dom =>
971 case Acl.whoHas {class = "domain", value = dom} of
972 [_] => true
973 | _ => false) (StringSet.listItems doms)
974 in
975 Acl.rmuser user;
976 Domain.rmdom doms
977 end
978
c9731b9b
AC
979fun now () = Date.toString (Date.fromTimeUniv (Time.now ()))
980
a95a0107
AC
981fun answerQuery q =
982 case q of
983 QApt pkg => if Apt.installed pkg then MsgYes else MsgNo
d351d679
AC
984 | QCron user => if Cron.allowed user then MsgYes else MsgNo
985 | QFtp user => if Ftp.allowed user then MsgYes else MsgNo
4d5126e1 986 | QTrustedPath user => if TrustedPath.query user then MsgYes else MsgNo
737c68d4 987 | QSocket user => MsgSocket (SocketPerm.query user)
f9548f16 988 | QFirewall user => MsgFirewall (Firewall.query user)
a95a0107
AC
989
990fun describeQuery q =
991 case q of
992 QApt pkg => "Requested installation status of package " ^ pkg
d351d679
AC
993 | QCron user => "Asked about cron permissions for user " ^ user
994 | QFtp user => "Asked about FTP permissions for user " ^ user
4d5126e1 995 | QTrustedPath user => "Asked about trusted path settings for user " ^ user
737c68d4 996 | QSocket user => "Asked about socket permissions for user " ^ user
f9548f16 997 | QFirewall user => "Asked about firewall rules for user " ^ user
a95a0107 998
3b267643 999fun service () =
07cc384c 1000 let
aa56e112
AC
1001 val () = Acl.read Config.aclFile
1002
d22c1f00
AC
1003 val context = context (Config.serverCert,
1004 Config.serverKey,
1005 Config.trustStore)
36e42cb8 1006 val _ = Domain.set_context context
3b267643 1007
60534712 1008 val sock = OpenSSL.listen (context, Config.dispatcherPort)
3b267643
AC
1009
1010 fun loop () =
60534712 1011 case OpenSSL.accept sock of
3b267643
AC
1012 NONE => ()
1013 | SOME bio =>
1014 let
aa56e112 1015 val user = OpenSSL.peerCN bio
c9731b9b 1016 val () = print ("\nConnection from " ^ user ^ " at " ^ now () ^ "\n")
aa56e112
AC
1017 val () = Domain.setUser user
1018
08688401
AC
1019 fun doIt f cleanup =
1020 ((case f () of
1021 (msgLocal, SOME msgRemote) =>
1022 (print msgLocal;
1023 print "\n";
1024 Msg.send (bio, MsgError msgRemote))
1025 | (msgLocal, NONE) =>
1026 (print msgLocal;
1027 print "\n";
1028 Msg.send (bio, MsgOk)))
64a44dc0 1029 handle e as (OpenSSL.OpenSSL s) =>
16465a9a
AC
1030 (print ("OpenSSL error: " ^ s ^ "\n");
1031 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
1032 Msg.send (bio, MsgError ("OpenSSL error: " ^ s))
1033 handle OpenSSL.OpenSSL _ => ())
08688401
AC
1034 | OS.SysErr (s, _) =>
1035 (print "System error: ";
1036 print s;
1037 print "\n";
1038 Msg.send (bio, MsgError ("System error: " ^ s))
1039 handle OpenSSL.OpenSSL _ => ())
1040 | Fail s =>
1041 (print "Failure: ";
1042 print s;
1043 print "\n";
1044 Msg.send (bio, MsgError ("Failure: " ^ s))
1045 handle OpenSSL.OpenSSL _ => ())
1046 | ErrorMsg.Error =>
1047 (print "Compilation error\n";
1048 Msg.send (bio, MsgError "Error during configuration evaluation")
1049 handle OpenSSL.OpenSSL _ => ());
1050 (cleanup ();
1051 ignore (OpenSSL.readChar bio);
1052 OpenSSL.close bio)
1053 handle OpenSSL.OpenSSL _ => ();
1054 loop ())
1055
c53e82e4
AC
1056 fun doConfig codes =
1057 let
1058 val _ = print "Configuration:\n"
1059 val _ = app (fn s => (print s; print "\n")) codes
1060 val _ = print "\n"
1061
1062 val outname = OS.FileSys.tmpName ()
1063
1064 fun doOne code =
1065 let
1066 val outf = TextIO.openOut outname
1067 in
1068 TextIO.output (outf, code);
1069 TextIO.closeOut outf;
1824f573 1070 eval' outname
c53e82e4
AC
1071 end
1072 in
08688401
AC
1073 doIt (fn () => (Env.pre ();
1074 app doOne codes;
1075 Env.post ();
1076 Msg.send (bio, MsgOk);
1077 ("Configuration complete.", NONE)))
1078 (fn () => OS.FileSys.remove outname)
c53e82e4
AC
1079 end
1080
2e96b9d4
AC
1081 fun checkAddr s =
1082 case String.fields (fn ch => ch = #"@") s of
1083 [user'] =>
1084 if user = user' then
1085 SOME (SetSA.User s)
1086 else
1087 NONE
1088 | [user', domain] =>
1089 if Domain.validEmailUser user' andalso Domain.yourDomain domain then
1090 SOME (SetSA.Email s)
1091 else
1092 NONE
1093 | _ => NONE
1094
36e42cb8
AC
1095 fun cmdLoop () =
1096 case Msg.recv bio of
1097 NONE => (OpenSSL.close bio
1098 handle OpenSSL.OpenSSL _ => ();
1099 loop ())
1100 | SOME m =>
1101 case m of
c53e82e4
AC
1102 MsgConfig code => doConfig [code]
1103 | MsgMultiConfig codes => doConfig codes
5ee41dd0 1104
9f27d58f 1105 | MsgShutdown =>
62260c5f
AC
1106 if Acl.query {user = user, class = "priv", value = "all"}
1107 orelse Acl.query {user = user, class = "priv", value = "shutdown"} then
c9731b9b 1108 print ("Domtool dispatcher shutting down at " ^ now () ^ "\n\n")
9f27d58f 1109 else
62260c5f
AC
1110 (print "Unauthorized shutdown command!\n";
1111 OpenSSL.close bio
9f27d58f
AC
1112 handle OpenSSL.OpenSSL _ => ();
1113 loop ())
1114
5ee41dd0 1115 | MsgGrant acl =>
08688401
AC
1116 doIt (fn () =>
1117 if Acl.query {user = user, class = "priv", value = "all"} then
1118 (Acl.grant acl;
1119 Acl.write Config.aclFile;
1120 ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".",
1121 NONE))
1122 else
1123 ("Unauthorized user asked to grant a permission!",
1124 SOME "Not authorized to grant privileges"))
1125 (fn () => ())
1126
411a85f2 1127 | MsgRevoke acl =>
08688401
AC
1128 doIt (fn () =>
1129 if Acl.query {user = user, class = "priv", value = "all"} then
1130 (Acl.revoke acl;
1131 Acl.write Config.aclFile;
1132 ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".",
1133 NONE))
1134 else
1135 ("Unauthorized user asked to revoke a permission!",
1136 SOME "Not authorized to revoke privileges"))
1137 (fn () => ())
5ee41dd0 1138
08a04eb4 1139 | MsgListPerms user =>
08688401
AC
1140 doIt (fn () =>
1141 (Msg.send (bio, MsgPerms (Acl.queryAll user));
1142 ("Sent permission list for user " ^ user ^ ".",
1143 NONE)))
1144 (fn () => ())
08a04eb4 1145
094877b1 1146 | MsgWhoHas perm =>
08688401
AC
1147 doIt (fn () =>
1148 (Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
1149 ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".",
1150 NONE)))
1151 (fn () => ())
094877b1 1152
e69e60cc 1153 | MsgRmdom doms =>
08688401
AC
1154 doIt (fn () =>
1155 if Acl.query {user = user, class = "priv", value = "all"}
1156 orelse List.all (fn dom => Acl.query {user = user, class = "domain", value = dom}) doms then
1157 (Domain.rmdom doms;
1158 app (fn dom =>
1159 Acl.revokeFromAll {class = "domain", value = dom}) doms;
1160 Acl.write Config.aclFile;
1161 ("Removed domains" ^ foldl (fn (d, s) => s ^ " " ^ d) "" doms ^ ".",
1162 NONE))
1163 else
1164 ("Unauthorized user asked to remove a domain!",
1165 SOME "Not authorized to remove that domain"))
1166 (fn () => ())
1824f573
AC
1167
1168 | MsgRegenerate =>
08688401
AC
1169 doIt (fn () =>
1170 if Acl.query {user = user, class = "priv", value = "regen"}
1171 orelse Acl.query {user = user, class = "priv", value = "all"} then
fb6fac97
AC
1172 (if regenerate context then
1173 ("Regenerated all configuration.",
1174 NONE)
1175 else
1176 ("Error regenerating configuration!",
1177 SOME "Error regenerating configuration! Consult /var/log/domtool.log."))
08688401
AC
1178 else
1179 ("Unauthorized user asked to regenerate!",
1180 SOME "Not authorized to regenerate"))
1181 (fn () => ())
e69e60cc 1182
fb6fac97
AC
1183 | MsgRegenerateTc =>
1184 doIt (fn () =>
1185 if Acl.query {user = user, class = "priv", value = "regen"}
1186 orelse Acl.query {user = user, class = "priv", value = "all"} then
1187 (if regenerateTc context then
1188 ("Checked all configuration.",
1189 NONE)
1190 else
1191 ("Found a compilation error!",
1192 SOME "Found a compilation error! Consult /var/log/domtool.log."))
1193 else
1194 ("Unauthorized user asked to regenerate -tc!",
1195 SOME "Not authorized to regenerate -tc"))
1196 (fn () => ())
1197
05323cbc 1198 | MsgRmuser user' =>
08688401
AC
1199 doIt (fn () =>
1200 if Acl.query {user = user, class = "priv", value = "all"} then
1201 (rmuser user';
1202 Acl.write Config.aclFile;
1203 ("Removed user " ^ user' ^ ".",
1204 NONE))
1205 else
1206 ("Unauthorized user asked to remove a user!",
1207 SOME "Not authorized to remove users"))
1208 (fn () => ())
d541c618 1209
21d921a5 1210 | MsgCreateDbUser {dbtype, passwd} =>
08688401
AC
1211 doIt (fn () =>
1212 case Dbms.lookup dbtype of
1213 NONE => ("Database user creation request with unknown datatype type " ^ dbtype,
1214 SOME ("Unknown database type " ^ dbtype))
1215 | SOME handler =>
1216 case #adduser handler {user = user, passwd = passwd} of
1217 NONE => ("Added " ^ dbtype ^ " user " ^ user ^ ".",
1218 NONE)
1219 | SOME msg =>
1220 ("Error adding a " ^ dbtype ^ " user " ^ user ^ ": " ^ msg,
1221 SOME ("Error adding user: " ^ msg)))
1222 (fn () => ())
c189cbe9 1223
86aa5de7
AC
1224 | MsgDbPasswd {dbtype, passwd} =>
1225 doIt (fn () =>
1226 case Dbms.lookup dbtype of
1227 NONE => ("Database passwd request with unknown datatype type " ^ dbtype,
1228 SOME ("Unknown database type " ^ dbtype))
1229 | SOME handler =>
1230 case #passwd handler {user = user, passwd = passwd} of
1231 NONE => ("Changed " ^ dbtype ^ " password of user " ^ user ^ ".",
1232 NONE)
1233 | SOME msg =>
1234 ("Error setting " ^ dbtype ^ " password of user " ^ user ^ ": " ^ msg,
1235 SOME ("Error adding user: " ^ msg)))
1236 (fn () => ())
1237
90dd48df 1238 | MsgCreateDbTable {dbtype, dbname} =>
08688401
AC
1239 doIt (fn () =>
1240 if Dbms.validDbname dbname then
1241 case Dbms.lookup dbtype of
1242 NONE => ("Database creation request with unknown datatype type " ^ dbtype,
1243 SOME ("Unknown database type " ^ dbtype))
1244 | SOME handler =>
1245 case #createdb handler {user = user, dbname = dbname} of
1246 NONE => ("Created database " ^ user ^ "_" ^ dbname ^ ".",
1247 NONE)
1248 | SOME msg => ("Error creating database " ^ user ^ "_" ^ dbname ^ ": " ^ msg,
1249 SOME ("Error creating database: " ^ msg))
1250 else
1251 ("Invalid database name " ^ user ^ "_" ^ dbname,
1252 SOME ("Invalid database name " ^ dbname)))
1253 (fn () => ())
1254
1d3ef80e
AC
1255 | MsgListMailboxes domain =>
1256 doIt (fn () =>
1257 if not (Domain.yourDomain domain) then
1258 ("User wasn't authorized to list mailboxes for " ^ domain,
1259 SOME "You're not authorized to configure that domain.")
1260 else
1261 case Vmail.list domain of
1262 Vmail.Listing users => (Msg.send (bio, MsgMailboxes users);
1263 ("Sent mailbox list for " ^ domain,
1264 NONE))
1265 | Vmail.Error msg => ("Error listing mailboxes for " ^ domain ^ ": " ^ msg,
1266 SOME msg))
1267 (fn () => ())
1268
08688401
AC
1269 | MsgNewMailbox {domain, user = emailUser, passwd, mailbox} =>
1270 doIt (fn () =>
1271 if not (Domain.yourDomain domain) then
1272 ("User wasn't authorized to add a mailbox to " ^ domain,
1273 SOME "You're not authorized to configure that domain.")
2e96b9d4 1274 else if not (Domain.validEmailUser emailUser) then
08688401
AC
1275 ("Invalid e-mail username " ^ emailUser,
1276 SOME "Invalid e-mail username")
1277 else if not (CharVector.all Char.isGraph passwd) then
1278 ("Invalid password",
1279 SOME "Invalid password; may only contain printable, non-space characters")
1280 else if not (Domain.yourPath mailbox) then
1281 ("User wasn't authorized to add a mailbox at " ^ mailbox,
1282 SOME "You're not authorized to use that mailbox location.")
1283 else
1284 case Vmail.add {requester = user,
1285 domain = domain, user = emailUser,
1286 passwd = passwd, mailbox = mailbox} of
1287 NONE => ("Added mailbox " ^ emailUser ^ "@" ^ domain ^ " at " ^ mailbox,
1288 NONE)
9ffe2f0f 1289 | SOME msg => ("Error adding mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
08688401
AC
1290 SOME msg))
1291 (fn () => ())
1292
1293 | MsgPasswdMailbox {domain, user = emailUser, passwd} =>
1294 doIt (fn () =>
1295 if not (Domain.yourDomain domain) then
1296 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1297 SOME "You're not authorized to configure that domain.")
2e96b9d4 1298 else if not (Domain.validEmailUser emailUser) then
08688401
AC
1299 ("Invalid e-mail username " ^ emailUser,
1300 SOME "Invalid e-mail username")
1301 else if not (CharVector.all Char.isGraph passwd) then
1302 ("Invalid password",
1303 SOME "Invalid password; may only contain printable, non-space characters")
1304 else
1305 case Vmail.passwd {domain = domain, user = emailUser,
1306 passwd = passwd} of
1307 NONE => ("Changed password of mailbox " ^ emailUser ^ "@" ^ domain,
1308 NONE)
9ffe2f0f 1309 | SOME msg => ("Error changing mailbox password for " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
08688401
AC
1310 SOME msg))
1311 (fn () => ())
1312
1313 | MsgRmMailbox {domain, user = emailUser} =>
1314 doIt (fn () =>
1315 if not (Domain.yourDomain domain) then
1316 ("User wasn't authorized to change password of a mailbox for " ^ domain,
1317 SOME "You're not authorized to configure that domain.")
2e96b9d4 1318 else if not (Domain.validEmailUser emailUser) then
08688401
AC
1319 ("Invalid e-mail username " ^ emailUser,
1320 SOME "Invalid e-mail username")
1321 else
1322 case Vmail.rm {domain = domain, user = emailUser} of
1323 NONE => ("Deleted mailbox " ^ emailUser ^ "@" ^ domain,
1324 NONE)
9ffe2f0f 1325 | SOME msg => ("Error deleting mailbox " ^ emailUser ^ "@" ^ domain ^ ": " ^ msg,
08688401
AC
1326 SOME msg))
1327 (fn () => ())
90dd48df 1328
2e96b9d4
AC
1329 | MsgSaQuery addr =>
1330 doIt (fn () =>
1331 case checkAddr addr of
1332 NONE => ("User tried to query SA filtering for " ^ addr,
1333 SOME "You aren't allowed to configure SA filtering for that recipient.")
1334 | SOME addr' => (Msg.send (bio, MsgSaStatus (SetSA.query addr'));
1335 ("Queried SA filtering status for " ^ addr,
1336 NONE)))
1337 (fn () => ())
1338
1339 | MsgSaSet (addr, b) =>
1340 doIt (fn () =>
1341 case checkAddr addr of
1342 NONE => ("User tried to set SA filtering for " ^ addr,
1343 SOME "You aren't allowed to configure SA filtering for that recipient.")
1344 | SOME addr' => (SetSA.set (addr', b);
1345 Msg.send (bio, MsgOk);
1346 ("Set SA filtering status for " ^ addr ^ " to "
1347 ^ (if b then "ON" else "OFF"),
1348 NONE)))
1349 (fn () => ())
1350
2bc5ed22
AC
1351 | MsgSmtpLogReq domain =>
1352 doIt (fn () =>
1353 if not (Domain.yourDomain domain) then
1354 ("Unauthorized user tried to request SMTP logs for " ^ domain,
1355 SOME "You aren't authorized to configure that domain.")
1356 else
1357 (SmtpLog.search (fn line => Msg.send (bio, MsgSmtpLogRes line))
1358 domain;
1359 ("Requested SMTP logs for " ^ domain,
1360 NONE)))
1361 (fn () => ())
1362
a95a0107
AC
1363 | MsgQuery q =>
1364 doIt (fn () => (Msg.send (bio, answerQuery q);
1365 (describeQuery q,
75585a67
AC
1366 NONE)))
1367 (fn () => ())
1368
36e42cb8 1369 | _ =>
08688401
AC
1370 doIt (fn () => ("Unexpected command",
1371 SOME "Unexpected command"))
1372 (fn () => ())
36e42cb8
AC
1373 in
1374 cmdLoop ()
1375 end
64a44dc0 1376 handle e as (OpenSSL.OpenSSL s) =>
97665758 1377 (print ("OpenSSL error: " ^ s ^ "\n");
64a44dc0 1378 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
97665758
AC
1379 OpenSSL.close bio
1380 handle OpenSSL.OpenSSL _ => ();
1381 loop ())
1382 | OS.SysErr (s, _) =>
1383 (print ("System error: " ^ s ^ "\n");
1384 OpenSSL.close bio
1385 handle OpenSSL.OpenSSL _ => ();
1386 loop ())
5cab5a98
AC
1387 | IO.Io {name, function, cause} =>
1388 (print ("IO error: " ^ function ^ " for " ^ name ^ "\n");
1389 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory cause);
1390 OpenSSL.close bio
1391 handle OpenSSL.OpenSSL _ => ();
1392 loop ())
e0b80e65 1393 | e =>
809b0173 1394 (print "Unknown exception in main loop!\n";
e0b80e65 1395 app (fn x => print (x ^ "\n")) (SMLofNJ.exnHistory e);
809b0173
AC
1396 OpenSSL.close bio
1397 handle OpenSSL.OpenSSL _ => ();
1398 loop ())
36e42cb8 1399 in
c9731b9b 1400 print ("Domtool dispatcher starting up at " ^ now () ^ "\n");
361a1e7f 1401 print "Listening for connections....\n";
36e42cb8
AC
1402 loop ();
1403 OpenSSL.shutdown sock
1404 end
1405
1406fun slave () =
1407 let
6e62228d 1408 val host = Slave.hostname ()
36e42cb8 1409
d22c1f00
AC
1410 val context = context (Config.certDir ^ "/" ^ host ^ ".pem",
1411 Config.keyDir ^ "/" ^ host ^ "/key.pem",
1412 Config.trustStore)
36e42cb8
AC
1413
1414 val sock = OpenSSL.listen (context, Config.slavePort)
1415
c9731b9b
AC
1416 val _ = print ("Slave server starting at " ^ now () ^ "\n")
1417
36e42cb8
AC
1418 fun loop () =
1419 case OpenSSL.accept sock of
1420 NONE => ()
1421 | SOME bio =>
1422 let
1423 val peer = OpenSSL.peerCN bio
c9731b9b 1424 val () = print ("\nConnection from " ^ peer ^ " at " ^ now () ^ "\n")
3b267643 1425 in
c9731b9b 1426 if peer = Config.dispatcherName then let
36e42cb8
AC
1427 fun loop' files =
1428 case Msg.recv bio of
1429 NONE => print "Dispatcher closed connection unexpectedly\n"
1430 | SOME m =>
1431 case m of
1432 MsgFile file => loop' (file :: files)
1433 | MsgDoFiles => (Slave.handleChanges files;
1434 Msg.send (bio, MsgOk))
71420f8b
AC
1435 | MsgRegenerate => (Domain.resetLocal ();
1436 Msg.send (bio, MsgOk))
36e42cb8
AC
1437 | _ => (print "Dispatcher sent unexpected command\n";
1438 Msg.send (bio, MsgError "Unexpected command"))
1439 in
1440 loop' [];
1441 ignore (OpenSSL.readChar bio);
1442 OpenSSL.close bio;
1443 loop ()
1444 end
c9731b9b
AC
1445 else if peer = "domtool" then
1446 case Msg.recv bio of
1447 SOME MsgShutdown => (OpenSSL.close bio;
1448 print ("Shutting down at " ^ now () ^ "\n\n"))
1449 | _ => (OpenSSL.close bio;
1450 loop ())
1451 else
a95a0107
AC
1452 case Msg.recv bio of
1453 SOME (MsgQuery q) => (print (describeQuery q ^ "\n");
1454 Msg.send (bio, answerQuery q);
1455 ignore (OpenSSL.readChar bio);
1456 OpenSSL.close bio;
1457 loop ())
1458 | _ => (OpenSSL.close bio;
1459 loop ())
3196000d
AC
1460 end handle OpenSSL.OpenSSL s =>
1461 (print ("OpenSSL error: "^ s ^ "\n");
1462 OpenSSL.close bio
1463 handle OpenSSL.OpenSSL _ => ();
1464 loop ())
95a9abd1
AC
1465 | e as OS.SysErr (s, _) =>
1466 (app (fn s => print (s ^ "\n")) (SMLofNJ.exnHistory e);
1467 print ("System error: "^ s ^ "\n");
7af7d4cb
AC
1468 OpenSSL.close bio
1469 handle OpenSSL.OpenSSL _ => ();
1470 loop ())
07cc384c 1471 in
3b267643
AC
1472 loop ();
1473 OpenSSL.shutdown sock
07cc384c
AC
1474 end
1475
44a5ce2f 1476fun listBasis () =
3196000d
AC
1477 let
1478 val dir = Posix.FileSys.opendir Config.libRoot
1479
1480 fun loop files =
1481 case Posix.FileSys.readdir dir of
1482 NONE => (Posix.FileSys.closedir dir;
1483 files)
1484 | SOME fname =>
1485 if String.isSuffix ".dtl" fname then
1486 loop (OS.Path.joinDirFile {dir = Config.libRoot,
1487 file = fname}
1488 :: files)
1489 else
1490 loop files
3196000d 1491 in
44a5ce2f 1492 loop []
3196000d
AC
1493 end
1494
44a5ce2f
AC
1495fun autodocBasis outdir =
1496 Autodoc.autodoc {outdir = outdir, infiles = listBasis ()}
1497
234b917a 1498end