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