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