Commit | Line | Data |
---|---|---|
182a2654 AC |
1 | (* |
2 | Domtool (http://hcoop.sf.net/) | |
3 | Copyright (C) 2004 Adam Chlipala | |
4 | ||
5 | This program is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU General Public License | |
7 | as published by the Free Software Foundation; either version 2 | |
8 | of the License, or (at your option) any later version. | |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
16 | along with this program; if not, write to the Free Software | |
17 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
18 | *) | |
19 | ||
20 | (* Apache vhost management module, with Webalizer support *) | |
21 | ||
22 | structure Apache :> APACHE = | |
23 | struct | |
24 | open Config ApacheConfig Util | |
25 | ||
26 | val vhosts = ref (NONE : TextIO.outstream option) | |
1e2e348e | 27 | val loggroups = ref (NONE : TextIO.outstream option) |
182a2654 | 28 | |
1e2e348e AC |
29 | fun init () = (vhosts := SOME (TextIO.openOut (scratchDir ^ "/vhosts.conf")); |
30 | loggroups := SOME (TextIO.openOut (scratchDir ^ "/loggroups"))) | |
182a2654 | 31 | fun finish () = (TextIO.closeOut (valOf (!vhosts)); |
1e2e348e AC |
32 | vhosts := NONE; |
33 | TextIO.closeOut (valOf (!loggroups)); | |
34 | loggroups := NONE) | |
182a2654 | 35 | |
05060d16 AC |
36 | val noargs = ["redirect", "R", "forbidden", "F", "gone", "G", "last", "L", "chain", "C", "nosubeq", "NS", "nocase", "NC", "qsappend", "QSA", "noescape", "NE", "passthrough", "PT"] |
37 | ||
38 | fun checkRewriteArgs (path, args) = | |
39 | if size args < 2 orelse String.sub (args, 0) <> #"[" orelse String.sub (args, size args - 1) <> #"]" then | |
40 | (Domtool.error (path, "Not in brackets: " ^ args); | |
41 | false) | |
42 | else let | |
43 | val args = String.substring (args, 1, size args - 2) | |
44 | val fields = String.fields (fn ch => ch = #",") args | |
45 | ||
46 | fun checkField f = | |
47 | case String.fields (fn ch => ch = #"=") f of | |
48 | [flag] => List.exists (fn x => x = flag) noargs orelse (Domtool.error (path, "Unknown argument-free flag " ^ flag); | |
49 | false) | |
05060d16 AC |
50 | | ["type", _] => true |
51 | | ["T", _] => true | |
52 | | ["skip", num] => isNat num orelse (Domtool.error (path, "Bad skip number " ^ num); false) | |
53 | | ["S", num] => isNat num orelse (Domtool.error (path, "Bad skip number " ^ num); false) | |
54 | | ["env", varval] => | |
55 | (case String.fields (fn ch => ch = #":") varval of | |
56 | [_, _] => true | |
57 | | _ => (Domtool.error (path, "Bad env setting " ^ varval); | |
58 | false)) | |
59 | | ["E", varval] => | |
60 | (case String.fields (fn ch => ch = #":") varval of | |
61 | [_, _] => true | |
62 | | _ => (Domtool.error (path, "Bad env setting " ^ varval); | |
63 | false)) | |
64 | | _ => (Domtool.error (path, "Unknown or disallowed mod_rewrite flag " ^ f); | |
65 | false) | |
66 | in | |
67 | List.all checkField fields | |
68 | end | |
69 | ||
182a2654 AC |
70 | fun handler {path, domain, parent, vars, paths, users, groups} = |
71 | let | |
72 | val _ = Domtool.dprint ("Reading host " ^ path ^ " for " ^ domain ^ "....") | |
73 | ||
74 | val vhosts = valOf (!vhosts) | |
1e2e348e | 75 | val loggroups = valOf (!loggroups) |
182a2654 AC |
76 | |
77 | val hf = TextIO.openIn path | |
78 | val rewrite = ref false | |
79 | ||
80 | val conf = TextIO.openOut (wblConfDir ^ "/" ^ domain ^ ".conf") | |
81 | val _ = TextIO.output (conf, "LogFile\t" ^ logDir ^ domain ^ "-access.log\n" ^ | |
82 | "OutputDir\t" ^ wblDocDir ^ "/" ^ domain ^ "\n" ^ | |
83 | "HostName\t" ^ domain ^ "\n" ^ | |
84 | "HideSite\t" ^ domain ^ "\n" ^ | |
85 | "HideReferrer\t" ^ domain ^ "\n") | |
86 | ||
87 | val dir = wblDocDir ^ "/" ^ domain | |
88 | val _ = | |
89 | if Posix.FileSys.access (dir, []) then | |
90 | () | |
91 | else | |
92 | Posix.FileSys.mkdir (dir, Posix.FileSys.S.flags [Posix.FileSys.S.ixoth, Posix.FileSys.S.irwxu, | |
93 | Posix.FileSys.S.irgrp, Posix.FileSys.S.iwgrp]) | |
94 | ||
95 | val htac = TextIO.openOut (dir ^ "/.htaccess") | |
96 | val user = ref defaultUser | |
97 | val group = ref defaultGroup | |
98 | ||
30ac0378 AC |
99 | val blocked = ref [] |
100 | ||
182a2654 AC |
101 | fun loop (line, ()) = |
102 | (case String.tokens Char.isSpace line of | |
103 | [] => () | |
104 | | ["User", user'] => | |
105 | if StringSet.member (users, user') then | |
106 | user := user' | |
107 | else | |
108 | Domtool.error (path, "not authorized to run as " ^ user') | |
109 | | ["Group", group'] => | |
110 | if StringSet.member (groups, group') then | |
1e2e348e AC |
111 | (group := group'; |
112 | TextIO.output (loggroups, domain ^ "\t" ^ group' ^ "\n")) | |
182a2654 AC |
113 | else |
114 | Domtool.error (path, "not authorized to run as group " ^ group') | |
115 | | ["ServerAdmin", email] => TextIO.output (vhosts, "\tServerAdmin " ^ email ^ "\n") | |
116 | | ["UserDir"] => TextIO.output (vhosts, "\tUserDir public_html\n\t<Directory /home/*/public_html/cgi-bin>\n\t\tAllowOverride None\n\t\tOptions ExecCGI\n\t\tAllow from all\n\t\tSetHandler cgi-script\n\t</Directory>\n\tScriptAliasMatch ^/~(.*)/cgi-bin/(.*) /home/$1/public_html/cgi-bin/$2\n") | |
117 | | ["DocumentRoot", p] => | |
118 | if checkPath (paths, p) then | |
119 | TextIO.output (vhosts, "\tDocumentRoot " ^ p ^ "\n") | |
120 | else | |
121 | print (path ^ ": not authorized to use " ^ p ^ "\n") | |
05060d16 AC |
122 | | "RewriteRule" :: src :: dst :: rest => |
123 | let | |
124 | val flags = | |
125 | case rest of | |
126 | [] => SOME "" | |
127 | | [flags] => if checkRewriteArgs (path, flags) then SOME flags else NONE | |
128 | | _ => (Domtool.error (path, "Invalid mod_rewrite flags in " ^ chop line); NONE) | |
129 | in | |
130 | case flags of | |
131 | SOME flags => | |
132 | (if not (!rewrite) then | |
133 | (rewrite := true; | |
134 | TextIO.output (vhosts, "\tRewriteEngine on\n")) | |
135 | else | |
136 | (); | |
137 | TextIO.output (vhosts, "\tRewriteRule\t" ^ src ^ " " ^ dst ^ " " ^ flags ^ "\n")) | |
138 | | _ => () | |
139 | end | |
140 | | ["LocalProxy", src, dst, port] => | |
141 | (case Int.fromString port of | |
142 | NONE => Domtool.error (path, "Invalid port number " ^ port) | |
143 | | SOME n => | |
144 | if n = 80 then | |
145 | Domtool.error (path, "No proxying back to Apache itself allowed") | |
146 | else if n <= 0 then | |
147 | Domtool.error (path, "Port number must be positive: " ^ port) | |
148 | else | |
1e2e348e AC |
149 | (if not (!rewrite) then |
150 | (rewrite := true; | |
151 | TextIO.output (vhosts, "\tRewriteEngine on\n")) | |
152 | else | |
153 | (); | |
154 | TextIO.output (vhosts, "\tRewriteRule\t" ^ src ^ " http://localhost:" ^ port ^ "/" ^ dst ^ " [P]\n"))) | |
182a2654 AC |
155 | | ["Alias", from, to] => |
156 | if checkPath (paths, to) then | |
157 | TextIO.output (vhosts, "\tAlias " ^ from ^ " " ^ to ^ "\n") | |
158 | else | |
159 | Domtool.error (path, "not authorized to use " ^ to) | |
160 | | "ErrorDocument" :: code :: rest => | |
161 | TextIO.output (vhosts, foldl (fn (a, s) => s ^ " " ^ a) ("\tErrorDocument " ^ code) rest ^ "\n") | |
162 | | ["ScriptAlias", from, to] => | |
163 | if checkPath (paths, to) then | |
164 | TextIO.output (vhosts, "\tScriptAlias " ^ from ^ " \"" ^ to ^ "\"\n") | |
165 | else | |
166 | Domtool.error (path, "not authorized to use " ^ to) | |
167 | | ["SSI"] => | |
168 | TextIO.output (vhosts, "\t<Location />\n\t\tOptions +Includes +IncludesNOEXEC\n\t</Location>\n") | |
169 | | ["ServerAlias", dom] => | |
170 | if validDomain dom then | |
171 | let | |
172 | val file = foldr (fn (c, s) => s ^ "/" ^ c) dataDir (String.fields (fn ch => ch = #".") dom) ^ ".aliased" | |
173 | in | |
174 | if Posix.FileSys.access (file, []) then | |
175 | (TextIO.output (vhosts, "\tServerAlias " ^ dom ^ "\n"); | |
176 | TextIO.output (conf, "HideSite\t" ^ dom ^ "\n" ^ | |
177 | "HideReferrer\t" ^ dom ^ "\n")) | |
178 | else | |
179 | Domtool.error (path, "not authorized to ServerAlias " ^ dom) | |
180 | end | |
181 | else | |
182 | Domtool.error (path, "bad host: " ^ dom) | |
183 | | "WebalizerUsers" :: users => | |
184 | TextIO.output (htac, "AuthType Basic\n" ^ | |
185 | "AuthName \"Abulafia web account\"\n" ^ | |
186 | "AuthUserFile " ^ passwdFile ^ "\n" ^ | |
187 | foldl (fn (u, s) => s ^ " " ^ u) "Require user" users ^ "\n") | |
188 | | ["AbuPrivate"] => TextIO.output (vhosts, | |
189 | "\t<Location />\n" ^ | |
190 | "\t\tAuthName \"Abulafia web account\"\n" ^ | |
191 | "\t\tAuthType basic\n" ^ | |
192 | "\t\tAuthUserFile " ^ passwdFile ^ "\n" ^ | |
193 | "\t\tRequire valid-user\n" ^ | |
194 | "\t\tOrder Deny,Allow\n" ^ | |
195 | "\t\tDeny from all\n" ^ | |
196 | "\t\tAllow from 127.0.0.1\n" ^ | |
197 | "\t\tAllow from 63.246.10.45\n" ^ | |
198 | "\t\tSatisfy any\n" ^ | |
199 | "\t</Location>\n") | |
30ac0378 | 200 | | ["Block", pat] => blocked := pat :: (!blocked) |
182a2654 AC |
201 | | ["Default"] => (TextIO.output (vhosts, "\tServerAlias " ^ parent ^ "\n"); |
202 | TextIO.output (conf, "HideSite\t" ^ parent ^ "\n" ^ | |
203 | "HideReferrer\t" ^ parent ^ "\n")) | |
204 | | ["CGI", p] => | |
205 | if checkPath (paths, p) then | |
206 | TextIO.output (vhosts, "\t<Directory " ^ p ^ ">\n" ^ | |
207 | "\t\tOptions ExecCGI\n" ^ | |
208 | "\t\tSetHandler cgi-script\n" ^ | |
209 | "\t</Directory>\n") | |
210 | else | |
211 | Domtool.error (path, "not authorized to use " ^ p) | |
212 | | ["HTML", p] => | |
213 | if checkPath (paths, p) then | |
214 | TextIO.output (vhosts, "\t<Directory " ^ p ^ ">\n" ^ | |
215 | "\t\tForceType text/html\n" ^ | |
216 | "\t</Directory>\n") | |
217 | else | |
218 | Domtool.error (path, "not authorized to use " ^ p) | |
219 | | cmd::_ => Domtool.error (path, "unknown option: " ^ cmd)) | |
220 | in | |
221 | TextIO.output (vhosts, "<VirtualHost *>\n" ^ | |
222 | "\tServerName " ^ domain ^ "\n" ^ | |
223 | "\tErrorLog " ^ logDir ^ domain ^ "-error.log\n" ^ | |
224 | "\tCustomLog " ^ logDir ^ domain ^ "-access.log combined\n" ^ | |
225 | "\tIndexOptions FancyIndexing FoldersFirst\n"); | |
05060d16 | 226 | ioOptLoop (fn () => Domtool.inputLine hf) loop (); |
30ac0378 AC |
227 | |
228 | (case !blocked of | |
229 | [] => () | |
230 | | _ => | |
231 | (TextIO.output (vhosts, | |
232 | "\t<Location />\n" ^ | |
233 | "\t\tOrder Allow,Deny\n" ^ | |
234 | "\t\tAllow from all\n"); | |
235 | app (fn pat => TextIO.output (vhosts, "\t\tDeny from " ^ pat ^ "\n")) (!blocked); | |
236 | TextIO.output (vhosts, "\t</Location>\n"))); | |
237 | ||
182a2654 AC |
238 | TextIO.output (vhosts, "\tUser "); |
239 | TextIO.output (vhosts, !user); | |
240 | TextIO.output (vhosts, "\n\tGroup "); | |
241 | TextIO.output (vhosts, !group); | |
242 | TextIO.output (vhosts, "\n</VirtualHost>\n\n"); | |
243 | TextIO.closeIn hf; | |
244 | TextIO.closeOut conf; | |
245 | TextIO.closeOut htac | |
246 | end handle Io => Domtool.error (path, "IO error") | |
247 | ||
248 | fun publish () = | |
249 | if OS.Process.isSuccess (OS.Process.system | |
250 | (diff ^ " " ^ scratchDir ^ "/vhosts.conf " ^ dataFile)) then | |
251 | OS.Process.success | |
252 | else if not (OS.Process.isSuccess (OS.Process.system | |
253 | (cp ^ " " ^ scratchDir ^ "/vhosts.conf " ^ dataFile))) then | |
254 | (print "Error copying vhosts.conf\n"; | |
255 | OS.Process.failure) | |
1e2e348e AC |
256 | else if not (OS.Process.isSuccess (OS.Process.system pubCommand)) then |
257 | (print "Error publishing vhosts.conf\n"; | |
258 | OS.Process.failure) | |
259 | else if OS.Process.isSuccess (OS.Process.system logpermsCommand) then | |
182a2654 AC |
260 | OS.Process.success |
261 | else | |
1e2e348e | 262 | (print "Error updating log permissions\n"; |
182a2654 AC |
263 | OS.Process.failure) |
264 | ||
265 | fun mkdom _ = OS.Process.success | |
266 | ||
267 | val _ = Domtool.setVhostHandler {init = init, | |
268 | file = handler, | |
269 | finish = finish, | |
270 | publish = publish, | |
271 | mkdom = mkdom} | |
272 | end | |
273 |