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