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