X-Git-Url: https://git.hcoop.net/hcoop/domtool2.git/blobdiff_plain/128e7b0b302e295411797f2465c508013bce4430..c6ef8d4e8d18c1af2cae226381a2e759133e1656:/src/plugins/apache.sml diff --git a/src/plugins/apache.sml b/src/plugins/apache.sml index 2d42a98..59e96b3 100644 --- a/src/plugins/apache.sml +++ b/src/plugins/apache.sml @@ -1,5 +1,6 @@ (* HCoop Domtool (http://hcoop.sourceforge.net/) - * Copyright (c) 2006-2007, Adam Chlipala + * Copyright (c) 2006-2009, Adam Chlipala + * Copyright (c) 2013,2014,2015,2017,2018,2019 Clinton Ebadi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -37,62 +38,84 @@ val _ = Env.registerFunction ("web_node_to_node", fn [e] => SOME e | _ => NONE) -fun webPlace (EApp ((EString "web_place_default", _), (EString node, _)), _) = - SOME (node, Domain.nodeIp node) - | webPlace (EApp ((EApp ((EString "web_place", _), (EString node, _)), _), (EString ip, _)), _) = - SOME (node, ip) +fun webPlace (EApp ((EVar "web_place_default", _), (EString node, _)), _) = + SOME (node, Domain.nodeIp node, Domain.nodeIpv6 node) + | webPlace (EApp ((EApp ((EApp ((EVar "web_place", _), (EString node, _)), _), (EString ip, _)), _), (EString ipv6, _)), _) = + SOME (node, ip, ipv6) | webPlace _ = NONE -fun webPlaceDefault node = (EApp ((EString "web_place_default", dl), (EString node, dl)), dl) +fun webPlaceDefault node = (EApp ((EVar "web_place_default", dl), (EString node, dl)), dl) val _ = Env.registerFunction ("web_place_to_web_node", - fn [e] => Option.map (fn (node, _) => (EString node, dl)) (webPlace e) + fn [e] => Option.map (fn (node, _, _) => (EString node, dl)) (webPlace e) | _ => NONE) val _ = Env.registerFunction ("web_place_to_node", - fn [e] => Option.map (fn (node, _) => (EString node, dl)) (webPlace e) + fn [e] => Option.map (fn (node, _, _) => (EString node, dl)) (webPlace e) | _ => NONE) val _ = Env.registerFunction ("web_place_to_ip", - fn [e] => Option.map (fn (_, ip) => (EString ip, dl)) (webPlace e) - | _ => NONE) + fn [e] => Option.map (fn (_, ip, _) => (EString ip, dl)) (webPlace e) + | _ => NONE) + +val _ = Env.registerFunction ("web_place_to_ipv6", + fn [e] => Option.map (fn (_, _, ipv6) => (EString ipv6, dl)) (webPlace e) + | _ => NONE) val _ = Env.type_one "proxy_port" Env.int (fn n => n > 1024) +fun validProxyTarget default s = + case String.fields (fn ch => ch = #":") s of + "http" :: host :: rest => + let + val rest = String.concatWith ":" rest + in + if List.exists (fn h' => host = h') (map (fn h => String.concat ["//", h]) Config.Apache.proxyHosts) + then + CharVector.all (fn ch => Char.isPrint ch andalso not (Char.isSpace ch) + andalso ch <> #"\"" andalso ch <> #"'") rest + andalso case String.fields (fn ch => ch = #"/") rest of + port :: _ => + (case Int.fromString port of + NONE => default s + | SOME n => n > 1024 orelse default s) + | _ => default s + else + default s + end + | _ => default s + val _ = Env.type_one "proxy_target" Env.string - (fn s => - let - fun default () = List.exists (fn s' => s = s') Config.Apache.proxyTargets - in - case String.fields (fn ch => ch = #":") s of - ["http", "//localhost", rest] => - (case String.fields (fn ch => ch = #"/") rest of - port :: _ => - (case Int.fromString port of - NONE => default () - | SOME n => n > 1024 orelse default ()) - | _ => default ()) - | _ => default () - end) + (validProxyTarget (fn s => List.exists (fn s' => s = s') (Config.Apache.proxyTargets @ ["!"]))) + +val _ = Env.type_one "proxy_reverse_target" + Env.string + (validProxyTarget (fn s => List.exists (fn s' => s = s') Config.Apache.proxyTargets)) val _ = Env.type_one "rewrite_arg" Env.string - (CharVector.all Char.isAlphaNum) + (* #":" is permitted here, but really ought to be disallowed or escaped for E=VAR:VAL *) + (CharVector.all (fn ch => (Char.isGraph ch) andalso not (List.exists (fn c => ch = c) [ #"[", #"]", #",", #"\"", #"'", #"=", #"\\" ]))) val _ = Env.type_one "suexec_flag" Env.bool (fn b => b orelse Domain.hasPriv "www") +val _ = Env.type_one "regexp" + Env.string + Pcre.validRegexp + fun validLocation s = size s > 0 andalso size s < 1000 andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"-" orelse ch = #"_" orelse ch = #"." - orelse ch = #"/") s + orelse ch = #"/" + orelse ch = #"~") s val _ = Env.type_one "location" Env.string @@ -102,38 +125,35 @@ fun validCert s = Acl.query {user = Domain.getUser (), class = "cert", value = s} +fun validCaCert s = Acl.query {user = Domain.getUser (), + class = "cacert", + value = s} + val _ = Env.type_one "ssl_cert_path" Env.string validCert +val _ = Env.type_one "ssl_cacert_path" + Env.string + validCaCert + fun ssl e = case e of (EVar "no_ssl", _) => SOME NONE | (EApp ((EVar "use_cert", _), s), _) => Option.map SOME (Env.string s) | _ => NONE -val defaults = [("WebPlaces", - (TList (TBase "web_place", dl), dl), - (fn () => (EList (map webPlaceDefault Config.Apache.webNodes_default), dl))), - ("SSL", - (TBase "ssl", dl), - (fn () => (EVar "no_ssl", dl))), - ("User", - (TBase "your_user", dl), - (fn () => (EString (Domain.getUser ()), dl))), - ("Group", - (TBase "your_group", dl), - (fn () => (EString "nogroup", dl))), - ("DocumentRoot", - (TBase "your_path", dl), - (fn () => (EString (Domain.homedir () ^ "/" ^ Config.Apache.public_html), dl))), - ("ServerAdmin", - (TBase "email", dl), - (fn () => (EString (Domain.getUser () ^ "@" ^ Config.defaultDomain), dl))), - ("SuExec", - (TBase "suexec_flag", dl), - (fn () => (EVar "true", dl)))] - -val () = app Defaults.registerDefault defaults +fun validExtension s = + size s > 0 + andalso size s < 20 + andalso CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"_") s + +val _ = Env.type_one "file_extension" + Env.string + validExtension + +val _ = Env.registerFunction ("defaultServerAdmin", + fn [] => SOME (EString (Domain.getUser () ^ "@" ^ Config.defaultDomain), dl) + | _ => NONE) val redirect_code = fn (EVar "temp", _) => SOME "temp" | (EVar "permanent", _) => SOME "permanent" @@ -145,6 +165,7 @@ val redirect_code = fn (EVar "temp", _) => SOME "temp" | (EVar "redir304", _) => SOME "304" | (EVar "redir305", _) => SOME "305" | (EVar "redir307", _) => SOME "307" + | (EVar "notfound", _) => SOME "404" | _ => NONE val flag = fn (EVar "redirect", _) => SOME "R" @@ -178,6 +199,8 @@ val cond_flag = fn (EVar "cond_nocase", _) => SOME "NC" val apache_option = fn (EVar "execCGI", _) => SOME "ExecCGI" | (EVar "includesNOEXEC", _) => SOME "IncludesNOEXEC" | (EVar "indexes", _) => SOME "Indexes" + | (EVar "followSymLinks", _) => SOME "FollowSymLinks" + | (EVar "multiViews", _) => SOME "MultiViews" | _ => NONE val autoindex_width = fn (EVar "autofit", _) => SOME "*" @@ -217,12 +240,27 @@ val autoindex_option = fn (EApp ((EVar "descriptionWidth", _), w), _) => | _ => NONE +val interval_base = fn (EVar "access", _) => SOME "access" + | (EVar "modification", _) => SOME "modification" + | _ => NONE + +val interval = fn (EVar "years", _) => SOME "years" + | (EVar "months", _) => SOME "months" + | (EVar "weeks", _) => SOME "weeks" + | (EVar "days", _) => SOME "days" + | (EVar "hours", _) => SOME "hours" + | (EVar "minutes", _) => SOME "minutes" + | (EVar "seconds", _) => SOME "seconds" + | _ => NONE + val vhostsChanged = ref false val logDeleted = ref false +val delayedLogMoves = ref (fn () => ()) val () = Slave.registerPreHandler (fn () => (vhostsChanged := false; - logDeleted := false)) + logDeleted := false; + delayedLogMoves := (fn () => print "Executing delayed log moves/deletes.\n"))) fun findVhostUser fname = let @@ -267,6 +305,13 @@ fun logDir {user, node, vhostId} = "/", vhostId] +fun realLogDir {user, node, vhostId} = + String.concat [Config.Apache.realLogDirOf user, + "/", + node, + "/", + vhostId] + val () = Slave.registerFileHandler (fn fs => let val spl = OS.Path.splitDirFile (#file fs) @@ -308,18 +353,20 @@ val () = Slave.registerFileHandler (fn fs => Slave.Delete _ => let val ldir = realLogDir oldUser + val dlm = !delayedLogMoves in if !logDeleted then () else - (ignore (OS.Process.system (down ())); + ((*ignore (OS.Process.system (down ()));*) ignore (OS.Process.system (fixperms ())); logDeleted := true); ignore (OS.Process.system (Config.rm ^ " -rf " ^ realVhostFile)); - Slave.moveDirCreate {from = ldir, - to = backupLogs ()} + delayedLogMoves := (fn () => (dlm (); + Slave.moveDirCreate {from = ldir, + to = backupLogs ()})) end | Slave.Add => let @@ -336,7 +383,7 @@ val () = Slave.registerFileHandler (fn fs => Slave.moveDirCreate {from = backupLogs (), to = rld} end - + | _ => (ignore (OS.Process.system (Config.cp ^ " " @@ -347,15 +394,18 @@ val () = Slave.registerFileHandler (fn fs => let val old = realLogDir oldUser val rld = realLogDir user + + val dlm = !delayedLogMoves in if !logDeleted then () else - (ignore (OS.Process.system (down ())); + ((*ignore (OS.Process.system (down ()));*) logDeleted := true); - ignore (OS.Process.system (Config.rm - ^ " -rf " - ^ realLogDir oldUser)); + delayedLogMoves := (fn () => (dlm (); + ignore (OS.Process.system (Config.rm + ^ " -rf " + ^ realLogDir oldUser)))); if Posix.FileSys.access (rld, []) then () else @@ -372,8 +422,9 @@ val () = Slave.registerFileHandler (fn fs => val () = Slave.registerPostHandler (fn () => (if !vhostsChanged then - Slave.shellF ([if !logDeleted then undown () else reload ()], - fn cl => "Error reloading Apache with " ^ cl) + (Slave.shellF ([reload ()], + fn cl => "Error reloading Apache with " ^ cl); + if !logDeleted then !delayedLogMoves () else ()) else ())) @@ -383,6 +434,8 @@ fun write s = app (fn (_, file) => TextIO.output (file, s)) (!vhostFiles) val rewriteEnabled = ref false val localRewriteEnabled = ref false +val expiresEnabled = ref false +val localExpiresEnabled = ref false val currentVhost = ref "" val currentVhostId = ref "" val sslEnabled = ref false @@ -414,104 +467,142 @@ fun registerAliaser f = aliaser := (fn x => (old x; f x)) end -val () = Env.containerV_one "vhost" - ("host", Env.string) - (fn (env, host) => - let - val places = Env.env (Env.list webPlace) (env, "WebPlaces") - - val ssl = Env.env ssl (env, "SSL") - val user = Env.env Env.string (env, "User") - val group = Env.env Env.string (env, "Group") - val docroot = Env.env Env.string (env, "DocumentRoot") - val sadmin = Env.env Env.string (env, "ServerAdmin") - val suexec = Env.env Env.bool (env, "SuExec") - - val fullHost = host ^ "." ^ Domain.currentDomain () - val vhostId = fullHost ^ (if Option.isSome ssl then ".ssl" else "") - val confFile = fullHost ^ (if Option.isSome ssl then ".vhost_ssl" else ".vhost") - in - currentVhost := fullHost; - currentVhostId := vhostId; - sslEnabled := Option.isSome ssl; +fun vhostPost () = (!post (); + write "\n"; + app (TextIO.closeOut o #2) (!vhostFiles)) - rewriteEnabled := false; - localRewriteEnabled := false; - vhostFiles := map (fn (node, ip) => - let - val file = Domain.domainFile {node = node, - name = confFile} +val php_version = fn (EVar "php56", _) => SOME 56 + | (EVar "php72", _) => SOME 72 + | (EVar "php73", _) => SOME 73 + | (EVar "php74", _) => SOME 74 + | (EVar "php80", _) => SOME 80 + | _ => NONE - val ld = logDir {user = user, node = node, vhostId = vhostId} - in - TextIO.output (file, "# Owner: "); +fun vhostBody (env, makeFullHost) = + let + val places = Env.env (Env.list webPlace) (env, "WebPlaces") + + val ssl = Env.env ssl (env, "SSL") + val user = Env.env Env.string (env, "User") + val group = Env.env Env.string (env, "Group") + val docroot = Env.env Env.string (env, "DocumentRoot") + val sadmin = Env.env Env.string (env, "ServerAdmin") + val suexec = Env.env Env.bool (env, "SuExec") + val php = Env.env php_version (env, "PhpVersion") + + val fullHost = makeFullHost (Domain.currentDomain ()) + val vhostId = fullHost ^ (if Option.isSome ssl then ".ssl" else "") + val confFile = fullHost ^ (if Option.isSome ssl then ".vhost_ssl" else ".vhost") + in + currentVhost := fullHost; + currentVhostId := vhostId; + sslEnabled := Option.isSome ssl; + + rewriteEnabled := false; + localRewriteEnabled := false; + expiresEnabled := false; + localExpiresEnabled := false; + vhostFiles := map (fn (node, ip, ipv6) => + let + val file = Domain.domainFile {node = node, + name = confFile} + + val ld = logDir {user = user, node = node, vhostId = vhostId} + in + TextIO.output (file, "# Owner: "); + TextIO.output (file, user); + TextIO.output (file, "\n "443" + | NONE => "80"); + + TextIO.output (file, " ["); + TextIO.output (file, ipv6); + TextIO.output (file, "]"); + TextIO.output (file, ":"); + TextIO.output (file, case ssl of + SOME _ => "443" + | NONE => "80"); + + TextIO.output (file, ">\n"); + TextIO.output (file, "\tErrorLog "); + TextIO.output (file, ld); + TextIO.output (file, "/error.log\n\tCustomLog "); + TextIO.output (file, ld); + TextIO.output (file, "/access.log combined\n"); + TextIO.output (file, "\tServerName "); + TextIO.output (file, fullHost); + app + (fn dom => (TextIO.output (file, "\n\tServerAlias "); + TextIO.output (file, makeFullHost dom))) + (Domain.currentAliasDomains ()); + + if suexec then + if isVersion1 node then + (TextIO.output (file, "\n\tUser "); TextIO.output (file, user); - TextIO.output (file, "\n "443" - | NONE => "80"); - TextIO.output (file, ">\n"); - TextIO.output (file, "\tErrorLog "); - TextIO.output (file, ld); - TextIO.output (file, "/error.log\n\tCustomLog "); - TextIO.output (file, ld); - TextIO.output (file, "/access.log combined\n"); - TextIO.output (file, "\tServerName "); - TextIO.output (file, fullHost); - app - (fn dom => (TextIO.output (file, "\n\tServerAlias "); - TextIO.output (file, host); - TextIO.output (file, "."); - TextIO.output (file, dom))) - (Domain.currentAliasDomains ()); - - if suexec then - if isVersion1 node then - (TextIO.output (file, "\n\tUser "); - TextIO.output (file, user); - TextIO.output (file, "\n\tGroup "); - TextIO.output (file, group)) - else - (TextIO.output (file, "\n\tSuexecUserGroup "); - TextIO.output (file, user); - TextIO.output (file, " "); - TextIO.output (file, group)) - else - (); - - if isWaklog node then - (TextIO.output (file, "\n\tWaklogEnabled on\n\tWaklogLocationPrincipal "); - TextIO.output (file, user); - TextIO.output (file, "/daemon@HCOOP.NET /etc/keytabs/user.daemon/"); - TextIO.output (file, user)) - else - (); - - TextIO.output (file, "\n\tDAVLockDB /var/lock/apache2/dav/"); + TextIO.output (file, "\n\tGroup "); + TextIO.output (file, group)) + else + (TextIO.output (file, "\n\tSuexecUserGroup "); TextIO.output (file, user); - TextIO.output (file, "/DAVLock"); + TextIO.output (file, " "); + TextIO.output (file, group)) + else + (); + + if isWaklog node then + (TextIO.output (file, "\n\tWaklogEnabled on\n\tWaklogLocationPrincipal "); + TextIO.output (file, user); + TextIO.output (file, "/daemon@HCOOP.NET /etc/keytabs/user.daemon/"); + TextIO.output (file, user)) + else + (); + + TextIO.output (file, "\n\tDAVLockDB /var/local/domtool/apache2/dav/"); + TextIO.output (file, user); + TextIO.output (file, "/DAVLock"); + + TextIO.output (file, "\n\tAddHandler fcgid-script .php .phtml"); + map (fn ext => (TextIO.output (file, "\n\tFcgidWrapper \""); + (* kerberos wrapper, simulates waklog+mod_cgi *) + if isWaklog node then + (TextIO.output (file, Config.Apache.fastCgiWrapperOf user); + TextIO.output (file, " ")) + else + (); + TextIO.output (file, Config.Apache.phpFastCgiWrapper php); + TextIO.output (file, "\" "); + TextIO.output (file, ext))) + [".php", ".phtml"]; + (ld, file) + end) + places; + write "\n\tDocumentRoot "; + write docroot; + write "\n\tServerAdmin "; + write sadmin; + case ssl of + SOME cert => + (write "\n\tSSLEngine on\n\tSSLCertificateFile "; + write cert) + | NONE => (); + write "\n"; + !pre {user = user, nodes = map #1 places, id = vhostId, hostname = fullHost}; + app (fn dom => !aliaser (makeFullHost dom)) (Domain.currentAliasDomains ()) + end - (ld, file) - end) - places; - write "\n\tDocumentRoot "; - write docroot; - write "\n\tServerAdmin "; - write sadmin; - case ssl of - SOME cert => - (write "\n\tSSLEngine on\n\tSSLCertificateFile "; - write cert) - | NONE => (); - write "\n"; - !pre {user = user, nodes = map #1 places, id = vhostId, hostname = fullHost}; - app (fn dom => !aliaser (host ^ "." ^ dom)) (Domain.currentAliasDomains ()) - end, - fn () => (!post (); - write "\n"; - app (TextIO.closeOut o #2) (!vhostFiles))) +val () = Env.containerV_one "vhost" + ("host", Env.string) + (fn (env, host) => vhostBody (env, fn dom => host ^ "." ^ dom), + vhostPost) + +val () = Env.containerV_none "vhostDefault" + (fn env => vhostBody (env, fn dom => dom), + vhostPost) val inLocal = ref false @@ -524,7 +615,8 @@ val () = Env.container_one "location" inLocal := true), fn () => (write "\t\n"; inLocal := false; - localRewriteEnabled := false)) + localRewriteEnabled := false; + localExpiresEnabled := false)) val () = Env.container_one "directory" ("directory", Env.string) @@ -535,11 +627,22 @@ val () = Env.container_one "directory" inLocal := true), fn () => (write "\t\n"; inLocal := false; - localRewriteEnabled := false)) + localRewriteEnabled := false; + localExpiresEnabled := false)) + +val () = Env.container_one "filesMatch" + ("regexp", Env.string) + (fn prefix => + (write "\t\n"), + fn () => (write "\t\n"; + localRewriteEnabled := false; + localExpiresEnabled := false)) fun checkRewrite () = if !inLocal then - if !rewriteEnabled orelse !localRewriteEnabled then + if !localRewriteEnabled then () else (write "\tRewriteEngine on\n"; @@ -550,17 +653,57 @@ fun checkRewrite () = (write "\tRewriteEngine on\n"; rewriteEnabled := true) -val () = Env.action_three "localProxyRewrite" - ("from", Env.string, "to", Env.string, "port", Env.int) - (fn (from, to, port) => +fun checkExpires () = + if !inLocal then + if !localExpiresEnabled then + () + else + (write "\tExpiresActive on\n"; + localExpiresEnabled := true) + else if !expiresEnabled then + () + else + (write "\tExpiresActive on\n"; + expiresEnabled := true) + +val () = Env.action_four "proxyRewrite" + ("from", Env.string, "to", Env.string, "tohost", Env.string, "flags", Env.list flag) + (fn (from, to, tohost, flags) => (checkRewrite (); - write "\tRewriteRule\t"; + write "\tRewriteRule\t\""; write from; - write "\thttp://localhost:"; - write (Int.toString port); - write "/"; + write "\"\t\""; + write tohost; + write "/"; (* ensure rewrite rule can't change port *) write to; - write " [P]\n")) + write "\""; + write " [P"; + case flags of + [] => () + | flag::rest => (write ","; + write flag; + app (fn flag => (write ","; + write flag)) rest); + + write "]\n")) + +val () = Env.action_four "expiresByType" + ("mime", Env.string, "base", interval_base, "num", Env.int, "inter", interval) + (fn (mime, base, num, inter) => + (checkExpires (); + write "\tExpiresByType\t\""; + write mime; + write "\"\t\""; + write base; + write " plus "; + if num < 0 then + (write "-"; + write (Int.toString (~num))) + else + write (Int.toString num); + write " "; + write inter; + write "\"\n")) val () = Env.action_two "proxyPass" ("from", Env.string, "to", Env.string) @@ -569,7 +712,7 @@ val () = Env.action_two "proxyPass" write from; write "\t"; write to; - write "\n")) + write "\tretry=0\n")) val () = Env.action_two "proxyPassReverse" ("from", Env.string, "to", Env.string) @@ -580,14 +723,22 @@ val () = Env.action_two "proxyPassReverse" write to; write "\n")) +val () = Env.action_one "proxyPreserveHost" + ("enable", Env.bool) + (fn (enable) => + (write "\tProxyPreserveHost\t"; + if enable then write "On" else write "Off"; + write "\n")) + val () = Env.action_three "rewriteRule" ("from", Env.string, "to", Env.string, "flags", Env.list flag) (fn (from, to, flags) => (checkRewrite (); - write "\tRewriteRule\t"; + write "\tRewriteRule\t\""; write from; - write "\t"; + write "\"\t\""; write to; + write "\""; case flags of [] => () | flag::rest => (write " ["; @@ -601,10 +752,11 @@ val () = Env.action_three "rewriteCond" ("test", Env.string, "pattern", Env.string, "flags", Env.list cond_flag) (fn (from, to, flags) => (checkRewrite (); - write "\tRewriteCond\t"; + write "\tRewriteCond\t\""; write from; - write "\t"; + write "\"\t\""; write to; + write "\""; case flags of [] => () | flag::rest => (write " ["; @@ -618,19 +770,24 @@ val () = Env.action_one "rewriteBase" ("prefix", Env.string) (fn prefix => (checkRewrite (); - write "\tRewriteBase\t"; + write "\tRewriteBase\t\""; write prefix; - write "\n")) + write "\"\n")) + +val _ = Env.type_one "mod_rewrite_trace_level" + Env.int + (fn n => n > 0 andalso n <= 8) val () = Env.action_one "rewriteLogLevel" ("level", Env.int) - (fn level => + (fn 0 => (checkRewrite (); - write "\tRewriteLog "; - write' (fn x => x); - write "/rewrite.log\n\tRewriteLogLevel "; - write (Int.toString level); - write "\n")) + write "\tLogLevel rewrite:warn\n") + | level => + (checkRewrite (); + write "\tLogLevel rewrite:trace"; + write (Int.toString level); + write "\n")) val () = Env.action_two "alias" ("from", Env.string, "to", Env.string) @@ -650,14 +807,62 @@ val () = Env.action_two "scriptAlias" write to; write "\n")) +val () = Env.action_two "fastScriptAlias" + ("from", Env.string, "to", Env.string) + (fn (from, to) => + let + (* mod_fcgid + kerberos limit this to working with + individual fcgi programs. assume the target path is a + file and any trailing `/' is just aliasing + syntax. Directory+File on the script is used to + activate fcgid instead of Location on the alias to + limit effects (alias+location also match in inverse + order causing pernicious side-effects *) + val fcgi_path = if String.sub (to, size to - 1) = #"/" + then + String.substring (to, 0, size to - 1) + else + to + val fcgi_dir = OS.Path.dir fcgi_path + val fcgi_file = OS.Path.file fcgi_path + in + write "\tAlias\t"; write from; write " "; write to; write "\n"; + + write "\t\n"; + write "\t\n"; + write "\tSetHandler fcgid-script\n"; + + (* FIXME: only set kerberos wrapper of waklog is on *) + (* won't be trivial, since we don't have access to node here *) + write "\tFcgidWrapper \""; + write (Config.Apache.fastCgiWrapperOf (Domain.getUser ())); + write " "; + write fcgi_path; + write "\"\n"; + + write "\t\n\t\n" + end) + val () = Env.action_two "errorDocument" ("code", Env.string, "handler", Env.string) (fn (code, handler) => - (write "\tErrorDocument\t"; - write code; - write " "; - write handler; - write "\n")) + let + val hasSpaces = CharVector.exists Char.isSpace handler + + fun maybeQuote () = + if hasSpaces then + write "\"" + else + () + in + write "\tErrorDocument\t"; + write code; + write " "; + maybeQuote (); + write handler; + maybeQuote (); + write "\n" + end) val () = Env.action_one "options" ("options", Env.list apache_option) @@ -686,6 +891,12 @@ val () = Env.action_one "unset_options" app (fn opt => (write " -"; write opt)) opts; write "\n")) +val () = Env.action_one "cgiExtension" + ("extension", Env.string) + (fn ext => (write "\tAddHandler cgi-script "; + write ext; + write "\n")) + val () = Env.action_one "directoryIndex" ("filenames", Env.list Env.string) (fn opts => @@ -693,6 +904,13 @@ val () = Env.action_one "directoryIndex" app (fn opt => (write " "; write opt)) opts; write "\n")) +val () = Env.action_one "directorySlash" + ("enable", Env.bool) + (fn enable => + (write "\tDirectorySlash "; + if enable then write "On" else write "Off"; + write "\n")) + val () = Env.action_one "serverAliasHost" ("host", Env.string) (fn host => @@ -742,8 +960,8 @@ val () = Env.action_one "authType" write ty; write "\n"; case ty of - "kerberos" => - write "\tKrbMethodNegotiate off\n\tKrbMethodK5Passwd on\n\tKrbVerifyKDC off\n\tKrbAuthRealms HCOOP.NET\n\tKrbSaveCredentials on\n" + "kerberos" => + write "\tKrbServiceName HTTP\n\tKrb5Keytab /etc/keytabs/service/apache\n\tKrbMethodNegotiate on\n\tKrbMethodK5Passwd on\n\tKrbVerifyKDC on\n\tKrbAuthRealms HCOOP.NET\n\tKrbSaveCredentials on\n" | _ => ()) else print "WARNING: Skipped Kerberos authType because this isn't an SSL vhost.\n") @@ -762,6 +980,13 @@ val () = Env.action_one "authUserFile" write name; write "\n")) +val () = Env.action_one "authGroupFile" + ("file", Env.string) + (fn name => + (write "\tAuthGroupFile "; + write name; + write "\n")) + val () = Env.action_none "requireValidUser" (fn () => write "\tRequire valid-user\n") @@ -868,6 +1093,17 @@ val () = Env.action_two "addDescription" app (fn pat => (write " "; write pat)) pats; write "\n")) +val () = Env.action_two "addIcon" + ("icon", Env.string, "patterns", Env.list Env.string) + (fn (icon, pats) => + case pats of + [] => () + | _ => (write "\tAddIcon \""; + write icon; + write "\""; + app (fn pat => (write " "; write pat)) pats; + write "\n")) + val () = Env.action_one "indexOptions" ("options", Env.list autoindex_option) (fn opts => @@ -881,6 +1117,15 @@ val () = Env.action_one "indexOptions" (write "="; write arg)) arg)) opts; write "\n")) +val () = Env.action_one "indexIgnore" + ("patterns", Env.list Env.string) + (fn pats => + case pats of + [] => () + | _ => (write "\tIndexIgnore"; + app (fn pat => (write " "; write pat)) pats; + write "\n")) + val () = Env.action_one "set_indexOptions" ("options", Env.list autoindex_option) (fn opts => @@ -926,13 +1171,121 @@ val () = Env.action_two "setEnv" | ch => str ch) value); write "\"\n")) +val () = Env.action_three "setEnvIf" + ("attribute", Env.string, "match", Env.string, "env_variables", Env.list Env.string) + (fn (attribute, match, envs) => + case envs of + [] => (print "WARNING: Skipped setEnvIf, no environment variables provided.\n") + | envs => + (write "\tSetEnvIf\t\""; + write attribute; + write "\"\t\""; + write match; + write "\""; + app (fn env => (write "\t"; write env)) envs; + write "\n")) + +val () = Env.action_three "setEnvIfNoCase" + ("attribute", Env.string, "match", Env.string, "env_variables", Env.list Env.string) + (fn (attribute, match, envs) => + case envs of + [] => (print "WARNING: Skipped setEnvIfNoCase, no environment variables provided.\n") + | envs => + (write "\tSetEnvIfNoCase\t\""; + write attribute; + write "\"\t\""; + write match; + write "\""; + app (fn env => (write "\t"; write env)) envs; + write "\n")) + +val () = Env.action_one "diskCache" + ("path", Env.string) + (fn path => (write "\tCacheEnable disk \""; + write path; + write "\"\n")) + +val () = Env.action_one "phpVersion" + ("version", php_version) + (fn version => (write "\tAddHandler fcgid-script .php .phtml\n"; + (* FIXME: only set kerberos wrapper of waklog is on *) + (* won't be trivial, since we don't have access to node here *) + app (fn ext => (write "\n\tFcgidWrapper \""; + write (Config.Apache.fastCgiWrapperOf (Domain.getUser ())); + write " "; + write (Config.Apache.phpFastCgiWrapper version); + write "\" "; + write ext; + write "\n")) + [".php", ".phtml"])) + +val () = Env.action_two "addType" + ("mime type", Env.string, "extension", Env.string) + (fn (mt, ext) => (write "\tAddType "; + write mt; + write " "; + write ext; + write "\n")) + +val filter = fn (EVar "includes", _) => SOME "INCLUDES" + | (EVar "deflate", _) => SOME "DEFLATE" + | _ => NONE + +val () = Env.action_two "addOutputFilter" + ("filters", Env.list filter, "extensions", Env.list Env.string) + (fn (f :: fs, exts as (_ :: _)) => + (write "\tAddOutputFilter "; + write f; + app (fn f => (write ";"; write f)) fs; + app (fn ext => (write " "; write ext)) exts; + write "\n") + | _ => ()) + +val () = Env.action_one "sslCertificateChainFile" + ("ssl_cacert_path", Env.string) + (fn cacert => + if !sslEnabled then + (write "\tSSLCertificateChainFile \""; + write cacert; + write "\"\n") + else + print "WARNING: Skipped sslCertificateChainFile because this isn't an SSL vhost.\n") + val () = Domain.registerResetLocal (fn () => - ignore (OS.Process.system (Config.rm ^ " -rf /var/domtool/vhosts/*"))) + ignore (OS.Process.system (Config.rm ^ " -rf " ^ Config.Apache.confDir ^ "/*"))) val () = Domain.registerDescriber (Domain.considerAll [Domain.Extension {extension = "vhost", - heading = fn host => "Web vhost " ^ host}, + heading = fn host => "Web vhost " ^ host ^ ":"}, Domain.Extension {extension = "vhost_ssl", - heading = fn host => "SSL web vhost " ^ host}]) + heading = fn host => "SSL web vhost " ^ host ^ ":"}]) + +val () = Env.action_one "allowEncodedSlashes" + ("enable", Env.bool) + (fn enable => (write "\tAllowEncodedSlashes "; + write (if enable then "NoDecode" else "Off"); + write "\n")) +val () = Env.action_none "testNoHtaccess" + (fn path => write "\tAllowOverride None\n") + +fun writeWaklogUserFile () = + let + val users = Acl.users () + val outf = TextIO.openOut Config.Apache.waklogUserFile + in + app (fn user => if String.isSuffix "_admin" user then + () + else + (TextIO.output (outf, "\n\tWaklogEnabled on\n\tWaklogLocationPrincipal "); + TextIO.output (outf, user); + TextIO.output (outf, "/daemon@HCOOP.NET /etc/keytabs/user.daemon/"); + TextIO.output (outf, user); + TextIO.output (outf, "\n\n\n"))) users; + TextIO.closeOut outf + end + +val () = Domain.registerOnUsersChange writeWaklogUserFile end