Server executing client's requested configuration with the right permissions
[hcoop/domtool2.git] / src / plugins / apache.sml
... / ...
CommitLineData
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *)
18
19(* Apache HTTPD handling *)
20
21structure Apache :> APACHE = struct
22
23open Ast
24
25val _ = Env.type_one "proxy_port"
26 Env.int
27 (fn n => n > 1024)
28
29val _ = Env.type_one "proxy_target"
30 Env.string
31 (fn s =>
32 let
33 fun default () = List.exists (fn s' => s = s') Config.Apache.proxyTargets
34 in
35 case String.fields (fn ch => ch = #":") s of
36 ["http", "//localhost", rest] =>
37 (case String.fields (fn ch => ch = #"/") rest of
38 port :: _ =>
39 (case Int.fromString port of
40 NONE => default ()
41 | SOME n => n > 1024 orelse default ())
42 | _ => default ())
43 | _ => default ()
44 end)
45
46val _ = Env.type_one "rewrite_arg"
47 Env.string
48 (CharVector.all Char.isAlphaNum)
49
50fun validLocation s =
51 size s > 0 andalso size s < 1000 andalso CharVector.all
52 (fn ch => Char.isAlphaNum ch
53 orelse ch = #"-"
54 orelse ch = #"_"
55 orelse ch = #"."
56 orelse ch = #"/") s
57
58val _ = Env.type_one "location"
59 Env.string
60 validLocation
61
62val dl = ErrorMsg.dummyLoc
63
64val _ = Defaults.registerDefault ("WebNodes",
65 (TList (TBase "node", dl), dl),
66 (fn () => (EList (map (fn s => (EString s, dl)) Config.Apache.webNodes), dl)))
67
68val _ = Defaults.registerDefault ("SSL",
69 (TBase "bool", dl),
70 (fn () => (EVar "false", dl)))
71
72val _ = Defaults.registerDefault ("User",
73 (TBase "your_user", dl),
74 (fn () => (EString (Domain.getUser ()), dl)))
75
76val _ = Defaults.registerDefault ("Group",
77 (TBase "your_group", dl),
78 (fn () => (EString (Domain.getUser ()), dl)))
79
80val _ = Defaults.registerDefault ("DocumentRoot",
81 (TBase "your_path", dl),
82 (fn () => (EString ("/home/" ^ Domain.getUser () ^ "/public_html"), dl)))
83
84val _ = Defaults.registerDefault ("ServerAdmin",
85 (TBase "email", dl),
86 (fn () => (EString (Domain.getUser () ^ "@" ^ Config.defaultDomain), dl)))
87
88
89val redirect_code = fn (EVar "temp", _) => SOME "temp"
90 | (EVar "permanent", _) => SOME "permanent"
91 | (EVar "seeother", _) => SOME "seeother"
92 | (EVar "redir300", _) => SOME "300"
93 | (EVar "redir301", _) => SOME "301"
94 | (EVar "redir302", _) => SOME "302"
95 | (EVar "redir303", _) => SOME "303"
96 | (EVar "redir304", _) => SOME "304"
97 | (EVar "redir305", _) => SOME "305"
98 | (EVar "redir307", _) => SOME "307"
99 | _ => NONE
100
101val flag = fn (EVar "redirect", _) => SOME "R"
102 | (EVar "forbidden", _) => SOME "F"
103 | (EVar "gone", _) => SOME "G"
104 | (EVar "last", _) => SOME "L"
105 | (EVar "chain", _) => SOME "C"
106 | (EVar "nosubreq", _) => SOME "NS"
107 | (EVar "nocase", _) => SOME "NC"
108 | (EVar "qsappend", _) => SOME "QSA"
109 | (EVar "noescape", _) => SOME "NE"
110 | (EVar "passthrough", _) => SOME "PT"
111 | (EApp ((EVar "mimeType", _), e), _) =>
112 Option.map (fn s => "T=" ^ s) (Env.string e)
113 | (EApp ((EVar "redirectWith", _), e), _) =>
114 Option.map (fn s => "R=" ^ s) (redirect_code e)
115 | (EApp ((EVar "skip", _), e), _) =>
116 Option.map (fn n => "S=" ^ Int.toString n) (Env.int e)
117 | (EApp ((EApp ((EVar "env", _), e1), _), e2), _) =>
118 (case Env.string e1 of
119 NONE => NONE
120 | SOME s1 => Option.map (fn s2 => "E=" ^ s1 ^ ":" ^ s2)
121 (Env.string e2))
122
123 | _ => NONE
124
125val cond_flag = fn (EVar "cond_nocase", _) => SOME "NC"
126 | (EVar "ornext", _) => SOME "OR"
127 | _ => NONE
128
129val apache_option = fn (EVar "execCGI", _) => SOME "ExecCGI"
130 | (EVar "includesNOEXEC", _) => SOME "IncludesNOEXEC"
131 | (EVar "indexes", _) => SOME "Indexes"
132 | _ => NONE
133
134val autoindex_width = fn (EVar "autofit", _) => SOME "*"
135 | (EApp ((EVar "characters", _), n), _) =>
136 Option.map Int.toString (Env.int n)
137 | _ => NONE
138
139val autoindex_option = fn (EApp ((EVar "descriptionWidth", _), w), _) =>
140 Option.map (fn w => ("DescriptionWidth", SOME w))
141 (autoindex_width w)
142 | (EVar "fancyIndexing", _) => SOME ("FancyIndexing", NONE)
143 | (EVar "foldersFirst", _) => SOME ("FoldersFirst", NONE)
144 | (EVar "htmlTable", _) => SOME ("HTMLTable", NONE)
145 | (EVar "iconsAreLinks", _) => SOME ("IconsAreLinks", NONE)
146 | (EApp ((EVar "iconHeight", _), n), _) =>
147 Option.map (fn w => ("IconHeight", SOME (Int.toString w)))
148 (Env.int n)
149 | (EApp ((EVar "iconWidth", _), n), _) =>
150 Option.map (fn w => ("IconWidth", SOME (Int.toString w)))
151 (Env.int n)
152 | (EVar "ignoreCase", _) => SOME ("IgnoreCase", NONE)
153 | (EVar "ignoreClient", _) => SOME ("IgnoreClient", NONE)
154 | (EApp ((EVar "nameWidth", _), w), _) =>
155 Option.map (fn w => ("NameWidth", SOME w))
156 (autoindex_width w)
157 | (EVar "scanHtmlTitles", _) => SOME ("ScanHTMLTitles", NONE)
158 | (EVar "suppressColumnSorting", _) => SOME ("SuppressColumnSorting", NONE)
159 | (EVar "suppressDescription", _) => SOME ("SuppressDescription", NONE)
160 | (EVar "suppressHtmlPreamble", _) => SOME ("SuppressHTMLPreamble", NONE)
161 | (EVar "suppressIcon", _) => SOME ("SuppressIcon", NONE)
162 | (EVar "suppressLastModified", _) => SOME ("SuppressLastModified", NONE)
163 | (EVar "suppressRules", _) => SOME ("SuppressRules", NONE)
164 | (EVar "suppressSize", _) => SOME ("SuppressSize", NONE)
165 | (EVar "trackModified", _) => SOME ("TrackModified", NONE)
166 | (EVar "versionSort", _) => SOME ("VersionSort", NONE)
167 | (EVar "xhtml", _) => SOME ("XHTML", NONE)
168
169 | _ => NONE
170
171val vhostsChanged = ref false
172
173val () = Slave.registerPreHandler
174 (fn () => vhostsChanged := false)
175
176val () = Slave.registerFileHandler (fn fs =>
177 let
178 val spl = OS.Path.splitDirFile (#file fs)
179 in
180 if String.isSuffix ".vhost" (#file spl)
181 orelse String.isSuffix ".vhost_ssl" (#file spl) then
182 (vhostsChanged := true;
183 case #action fs of
184 Slave.Delete =>
185 (ignore (OS.Process.system (Config.rm
186 ^ " -rf "
187 ^ Config.Apache.confDir
188 ^ "/"
189 ^ #file spl));
190 ignore (OS.Process.system (Config.rm
191 ^ " -rf "
192 ^ Config.Apache.logDir
193 ^ "/"
194 ^ #base (OS.Path.splitBaseExt
195 (#file spl)))))
196
197 | _ =>
198 ignore (OS.Process.system (Config.cp
199 ^ " "
200 ^ #file fs
201 ^ " "
202 ^ Config.Apache.confDir
203 ^ "/"
204 ^ #file spl)))
205 else
206 ()
207 end)
208
209val () = Slave.registerPostHandler
210 (fn () =>
211 (if !vhostsChanged then
212 Slave.shellF ([Config.Apache.reload],
213 fn cl => "Error reloading Apache with " ^ cl)
214 else
215 ()))
216
217val vhostFiles : TextIO.outstream list ref = ref []
218fun write s = app (fn file => TextIO.output (file, s)) (!vhostFiles)
219
220val rewriteEnabled = ref false
221val currentVhost = ref ""
222val currentVhostId = ref ""
223
224val pre = ref (fn _ : {nodes : string list, id : string, hostname : string} => ())
225fun registerPre f =
226 let
227 val old = !pre
228 in
229 pre := (fn x => (old x; f x))
230 end
231
232val post = ref (fn () => ())
233fun registerPost f =
234 let
235 val old = !post
236 in
237 post := (fn () => (old (); f ()))
238 end
239
240val aliaser = ref (fn _ : string => ())
241fun registerAliaser f =
242 let
243 val old = !aliaser
244 in
245 aliaser := (fn x => (old x; f x))
246 end
247
248val () = Env.containerV_one "vhost"
249 ("host", Env.string)
250 (fn (env, host) =>
251 let
252 val nodes = Env.env (Env.list Env.string) (env, "WebNodes")
253
254 val ssl = Env.env Env.bool (env, "SSL")
255 val user = Env.env Env.string (env, "User")
256 val group = Env.env Env.string (env, "Group")
257 val docroot = Env.env Env.string (env, "DocumentRoot")
258 val sadmin = Env.env Env.string (env, "ServerAdmin")
259
260 val fullHost = host ^ "." ^ Domain.currentDomain ()
261 val vhostId = fullHost ^ (if ssl then ".ssl" else "")
262 val confFile = fullHost ^ (if ssl then ".vhost_ssl" else ".vhost")
263 in
264 currentVhost := fullHost;
265 currentVhostId := vhostId;
266
267 rewriteEnabled := false;
268 vhostFiles := map (fn node =>
269 let
270 val file = Domain.domainFile {node = node,
271 name = confFile}
272 in
273 TextIO.output (file, "<VirtualHost ");
274 TextIO.output (file, Domain.nodeIp node);
275 TextIO.output (file, ":");
276 TextIO.output (file, if ssl then
277 "443"
278 else
279 "80");
280 TextIO.output (file, ">\n");
281 file
282 end)
283 nodes;
284 write "\tServerName ";
285 write fullHost;
286 write "\n\tSuexecUserGroup ";
287 write user;
288 write " ";
289 write group;
290 write "\n\tDocumentRoot ";
291 write docroot;
292 write "\n\tServerAdmin ";
293 write sadmin;
294 write "\n\tErrorLog ";
295 write Config.Apache.logDir;
296 write "/";
297 write vhostId;
298 write "/error.log\n\tCustomLog ";
299 write Config.Apache.logDir;
300 write "/";
301 write vhostId;
302 write "/access.log combined\n";
303 !pre {nodes = nodes, id = vhostId, hostname = fullHost}
304 end,
305 fn () => (!post ();
306 write "</VirtualHost>\n";
307 app TextIO.closeOut (!vhostFiles)))
308
309val () = Env.container_one "location"
310 ("prefix", Env.string)
311 (fn prefix =>
312 (write "\t<Location ";
313 write prefix;
314 write ">\n"),
315 fn () => write "\t</Location>\n")
316
317val () = Env.container_one "directory"
318 ("directory", Env.string)
319 (fn directory =>
320 (write "\t<Directory ";
321 write directory;
322 write ">\n"),
323 fn () => write "\t</Directory>\n")
324
325fun checkRewrite () =
326 if !rewriteEnabled then
327 ()
328 else
329 (write "\tRewriteEngine on\n";
330 rewriteEnabled := true)
331
332val () = Env.action_three "localProxyRewrite"
333 ("from", Env.string, "to", Env.string, "port", Env.int)
334 (fn (from, to, port) =>
335 (checkRewrite ();
336 write "\tRewriteRule\t";
337 write from;
338 write "\thttp://localhost:";
339 write (Int.toString port);
340 write "/";
341 write to;
342 write " [P]\n"))
343
344val () = Env.action_two "proxyPass"
345 ("from", Env.string, "to", Env.string)
346 (fn (from, to) =>
347 (write "\tProxyPass\t";
348 write from;
349 write "\t";
350 write to;
351 write "\n"))
352
353val () = Env.action_two "proxyPassReverse"
354 ("from", Env.string, "to", Env.string)
355 (fn (from, to) =>
356 (write "\tProxyPassReverse\t";
357 write from;
358 write "\t";
359 write to;
360 write "\n"))
361
362val () = Env.action_three "rewriteRule"
363 ("from", Env.string, "to", Env.string, "flags", Env.list flag)
364 (fn (from, to, flags) =>
365 (checkRewrite ();
366 write "\tRewriteRule\t";
367 write from;
368 write "\t";
369 write to;
370 case flags of
371 [] => ()
372 | flag::rest => (write " [";
373 write flag;
374 app (fn flag => (write ",";
375 write flag)) rest;
376 write "]");
377 write "\n"))
378
379val () = Env.action_three "rewriteCond"
380 ("test", Env.string, "pattern", Env.string, "flags", Env.list cond_flag)
381 (fn (from, to, flags) =>
382 (checkRewrite ();
383 write "\tRewriteCond\t";
384 write from;
385 write "\t";
386 write to;
387 case flags of
388 [] => ()
389 | flag::rest => (write " [";
390 write flag;
391 app (fn flag => (write ",";
392 write flag)) rest;
393 write "]");
394 write "\n"))
395
396val () = Env.action_one "rewriteLogLevel"
397 ("level", Env.int)
398 (fn level =>
399 (checkRewrite ();
400 write "\tRewriteLog ";
401 write Config.Apache.logDir;
402 write "/";
403 write (!currentVhostId);
404 write "/rewrite.log\n\tRewriteLogLevel ";
405 write (Int.toString level);
406 write "\n"))
407
408val () = Env.action_two "alias"
409 ("from", Env.string, "to", Env.string)
410 (fn (from, to) =>
411 (write "\tAlias\t";
412 write from;
413 write " ";
414 write to;
415 write "\n"))
416
417val () = Env.action_two "scriptAlias"
418 ("from", Env.string, "to", Env.string)
419 (fn (from, to) =>
420 (write "\tScriptAlias\t";
421 write from;
422 write " ";
423 write to;
424 write "\n"))
425
426val () = Env.action_two "errorDocument"
427 ("code", Env.string, "handler", Env.string)
428 (fn (code, handler) =>
429 (write "\tErrorDocument\t";
430 write code;
431 write " ";
432 write handler;
433 write "\n"))
434
435val () = Env.action_one "options"
436 ("options", Env.list apache_option)
437 (fn opts =>
438 case opts of
439 [] => ()
440 | _ => (write "\tOptions";
441 app (fn opt => (write " "; write opt)) opts;
442 write "\n"))
443
444val () = Env.action_one "set_options"
445 ("options", Env.list apache_option)
446 (fn opts =>
447 case opts of
448 [] => ()
449 | _ => (write "\tOptions";
450 app (fn opt => (write " +"; write opt)) opts;
451 write "\n"))
452
453val () = Env.action_one "unset_options"
454 ("options", Env.list apache_option)
455 (fn opts =>
456 case opts of
457 [] => ()
458 | _ => (write "\tOptions";
459 app (fn opt => (write " -"; write opt)) opts;
460 write "\n"))
461
462val () = Env.action_one "directoryIndex"
463 ("filenames", Env.list Env.string)
464 (fn opts =>
465 (write "\tDirectoryIndex";
466 app (fn opt => (write " "; write opt)) opts;
467 write "\n"))
468
469val () = Env.action_one "serverAlias"
470 ("host", Env.string)
471 (fn host =>
472 (write "\tServerAlias ";
473 write host;
474 write "\n";
475 !aliaser host))
476
477val authType = fn (EVar "basic", _) => SOME "basic"
478 | (EVar "digest", _) => SOME "digest"
479 | _ => NONE
480
481val () = Env.action_one "authType"
482 ("type", authType)
483 (fn ty =>
484 (write "\tAuthType ";
485 write ty;
486 write "\n"))
487
488val () = Env.action_one "authName"
489 ("name", Env.string)
490 (fn name =>
491 (write "\tAuthName \"";
492 write name;
493 write "\"\n"))
494
495val () = Env.action_one "authUserFile"
496 ("file", Env.string)
497 (fn name =>
498 (write "\tAuthUserFile ";
499 write name;
500 write "\n"))
501
502val () = Env.action_none "requireValidUser"
503 (fn () => write "\tRequire valid-user\n")
504
505val () = Env.action_one "requireUser"
506 ("users", Env.list Env.string)
507 (fn names =>
508 case names of
509 [] => ()
510 | _ => (write "\tRequire user";
511 app (fn name => (write " "; write name)) names;
512 write "\n"))
513
514val () = Env.action_one "requireGroup"
515 ("groups", Env.list Env.string)
516 (fn names =>
517 case names of
518 [] => ()
519 | _ => (write "\tRequire group";
520 app (fn name => (write " "; write name)) names;
521 write "\n"))
522
523val () = Env.action_none "orderAllowDeny"
524 (fn () => write "\tOrder allow,deny\n")
525
526val () = Env.action_none "orderDenyAllow"
527 (fn () => write "\tOrder deny,allow\n")
528
529val () = Env.action_none "allowFromAll"
530 (fn () => write "\tAllow from all\n")
531
532val () = Env.action_one "allowFrom"
533 ("entries", Env.list Env.string)
534 (fn names =>
535 case names of
536 [] => ()
537 | _ => (write "\tAllow from";
538 app (fn name => (write " "; write name)) names;
539 write "\n"))
540
541val () = Env.action_none "denyFromAll"
542 (fn () => write "\tDeny from all\n")
543
544val () = Env.action_one "denyFrom"
545 ("entries", Env.list Env.string)
546 (fn names =>
547 case names of
548 [] => ()
549 | _ => (write "\tDeny from";
550 app (fn name => (write " "; write name)) names;
551 write "\n"))
552
553val () = Env.action_none "satisfyAll"
554 (fn () => write "\tSatisfy all\n")
555
556val () = Env.action_none "satisfyAny"
557 (fn () => write "\tSatisfy any\n")
558
559val () = Env.action_one "forceType"
560 ("type", Env.string)
561 (fn ty => (write "\tForceType ";
562 write ty;
563 write "\n"))
564
565val () = Env.action_none "forceTypeOff"
566 (fn () => write "\tForceType None\n")
567
568val () = Env.action_two "action"
569 ("what", Env.string, "how", Env.string)
570 (fn (what, how) => (write "\tAction ";
571 write what;
572 write " ";
573 write how;
574 write "\n"))
575
576val () = Env.action_one "addDefaultCharset"
577 ("charset", Env.string)
578 (fn ty => (write "\tAddDefaultCharset ";
579 write ty;
580 write "\n"))
581
582val () = Env.action_one "davSvn"
583 ("path", Env.string)
584 (fn path => (write "\tDAV svn\n\tSVNPath ";
585 write path;
586 write "\n"))
587
588val () = Env.action_one "authzSvnAccessFile"
589 ("path", Env.string)
590 (fn path => (write "\tAuthzSVNAccessFile ";
591 write path;
592 write "\n"))
593
594val () = Env.action_two "addDescription"
595 ("description", Env.string, "patterns", Env.list Env.string)
596 (fn (desc, pats) =>
597 case pats of
598 [] => ()
599 | _ => (write "\tAddDescription \"";
600 write (String.toString desc);
601 write "\"";
602 app (fn pat => (write " "; write pat)) pats;
603 write "\n"))
604
605val () = Env.action_one "indexOptions"
606 ("options", Env.list autoindex_option)
607 (fn opts =>
608 case opts of
609 [] => ()
610 | _ => (write "\tIndexOptions";
611 app (fn (opt, arg) =>
612 (write " ";
613 write opt;
614 Option.app (fn arg =>
615 (write "="; write arg)) arg)) opts;
616 write "\n"))
617
618val () = Env.action_one "set_indexOptions"
619 ("options", Env.list autoindex_option)
620 (fn opts =>
621 case opts of
622 [] => ()
623 | _ => (write "\tIndexOptions";
624 app (fn (opt, arg) =>
625 (write " +";
626 write opt;
627 Option.app (fn arg =>
628 (write "="; write arg)) arg)) opts;
629 write "\n"))
630
631val () = Env.action_one "unset_indexOptions"
632 ("options", Env.list autoindex_option)
633 (fn opts =>
634 case opts of
635 [] => ()
636 | _ => (write "\tIndexOptions";
637 app (fn (opt, _) =>
638 (write " -";
639 write opt)) opts;
640 write "\n"))
641
642val () = Env.action_one "headerName"
643 ("name", Env.string)
644 (fn name => (write "\tHeaderName ";
645 write name;
646 write "\n"))
647
648val () = Env.action_one "readmeName"
649 ("name", Env.string)
650 (fn name => (write "\tReadmeName ";
651 write name;
652 write "\n"))
653
654end