2 Domtool (http
://hcoop
.sf
.net
/)
3 Copyright (C
) 2004 Adam Chlipala
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
.
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
.
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
.
20 (* Apache vhost management module
, with Webalizer support
*)
22 structure Apache
:> APACHE
=
24 open Config ApacheConfig Util
26 val vhosts
= ref (NONE
: TextIO.outstream option
)
27 val loggroups
= ref (NONE
: TextIO.outstream option
)
29 fun init () = (vhosts
:= SOME (TextIO.openOut (scratchDir ^
"/vhosts.conf"));
30 loggroups
:= SOME (TextIO.openOut (scratchDir ^
"/loggroups")))
31 fun finish () = (TextIO.closeOut (valOf (!vhosts
));
33 TextIO.closeOut (valOf (!loggroups
));
36 val noargs
= ["redirect", "R", "forbidden", "F", "gone", "G", "last", "L", "chain", "C", "nosubeq", "NS", "nocase", "NC", "qsappend", "QSA", "noescape", "NE", "passthrough", "PT"]
38 fun checkRewriteCondArgs (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
);
43 val args
= String.substring (args
, 1, size args
- 2)
44 val fields
= String.fields (fn ch
=> ch
= #
",") args
54 List.all checkOne fields
58 fun checkRewriteArgs (path
, args
) =
59 if size args
< 2 orelse String.sub (args
, 0) <> #
"[" orelse String.sub (args
, size args
- 1) <> #
"]" then
60 (Domtool
.error (path
, "Not in brackets: " ^ args
);
63 val args
= String.substring (args
, 1, size args
- 2)
64 val fields
= String.fields (fn ch
=> ch
= #
",") args
67 case String.fields (fn ch
=> ch
= #
"=") f
of
68 [flag
] => List.exists (fn x
=> x
= flag
) noargs
orelse (Domtool
.error (path
, "Unknown argument-free flag " ^ flag
);
72 |
["skip", num
] => isNat num
orelse (Domtool
.error (path
, "Bad skip number " ^ num
); false)
73 |
["S", num
] => isNat num
orelse (Domtool
.error (path
, "Bad skip number " ^ num
); false)
75 (case String.fields (fn ch
=> ch
= #
":") varval
of
77 | _
=> (Domtool
.error (path
, "Bad env setting " ^ varval
);
80 (case String.fields (fn ch
=> ch
= #
":") varval
of
82 | _
=> (Domtool
.error (path
, "Bad env setting " ^ varval
);
84 | _
=> (Domtool
.error (path
, "Unknown or disallowed mod_rewrite flag " ^ f
);
87 List.all checkField fields
90 fun handler
{path
, domain
, parent
, vars
, paths
, users
, groups
, mxs
} =
92 val _
= Domtool
.dprint ("Reading host " ^ path ^
" for " ^ domain ^
"....")
94 val vhosts
= valOf (!vhosts
)
95 val loggroups
= valOf (!loggroups
)
97 val domfile
= dataDir ^
"/" ^
String.concatWith
"/" (rev (String.tokens (fn ch
=> ch
= #
".") domain
))
98 val stat
= Posix
.FileSys
.stat domfile
99 val group
' = Posix
.SysDB
.Group
.name (Posix
.SysDB
.getgrgid (Posix
.FileSys
.ST
.gid stat
))
101 val _
= TextIO.output (loggroups
, domain ^
"\t" ^ group
' ^
"\n")
103 val hf
= TextIO.openIn path
104 val rewrite
= ref
false
106 val conf
= TextIO.openOut (wblConfDir ^
"/" ^ domain ^
".conf")
107 val _
= TextIO.output (conf
, "LogFile\t" ^ logDir ^ domain ^
"-access.log\n" ^
108 "OutputDir\t" ^ wblDocDir ^
"/" ^ domain ^
"\n" ^
109 "HostName\t" ^ domain ^
"\n" ^
110 "HideSite\t" ^ domain ^
"\n" ^
111 "HideReferrer\t" ^ domain ^
"\n")
113 val dir
= wblDocDir ^
"/" ^ domain
115 if Posix
.FileSys
.access (dir
, []) then
118 Posix
.FileSys
.mkdir (dir
, Posix
.FileSys
.S
.flags
[Posix
.FileSys
.S
.ixoth
, Posix
.FileSys
.S
.irwxu
,
119 Posix
.FileSys
.S
.irgrp
, Posix
.FileSys
.S
.iwgrp
])
121 val htac
= TextIO.openOut (dir ^
"/.htaccess")
122 val user
= ref defaultUser
123 val group
= ref defaultGroup
124 val scripts
= ref
false
129 val fixup
= ref
false
136 TextIO.output (vhosts
, "\tPerlFixupHandler Apache::PerlVINC\n");
137 TextIO.output (vhosts
, "\tPerlCleanupHandler Apache::PerlVINC\n"))
140 fun checkRewrite () =
141 if not (!rewrite
) then
143 TextIO.output (vhosts
, "\tRewriteEngine on\n"))
147 fun loop (line
, ()) =
148 (case String.tokens
Char.isSpace line
of
151 if StringSet
.member (users
, user
') then
154 Domtool
.error (path
, "not authorized to run as " ^ user
')
155 |
["Group", group
'] =>
156 if StringSet
.member (groups
, group
') then
159 Domtool
.error (path
, "not authorized to run as group " ^ group
')
160 |
["ServerAdmin", email
] => TextIO.output (vhosts
, "\tServerAdmin " ^ email ^
"\n")
161 (*|
["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")*)
162 |
["DocumentRoot", p
] =>
163 if checkPath (paths
, p
) then
164 TextIO.output (vhosts
, "\tDocumentRoot " ^ p ^
"\n")
166 print (path ^
": not authorized to use " ^ p ^
"\n")
167 |
"RewriteRule" :: src
:: dst
:: rest
=>
172 |
[flags
] => if checkRewriteArgs (path
, flags
) then SOME flags
else NONE
173 | _
=> (Domtool
.error (path
, "Invalid mod_rewrite flags in " ^ chop line
); NONE
)
178 TextIO.output (vhosts
, "\tRewriteRule\t" ^ src ^
" " ^ dst ^
" " ^ flags ^
"\n"))
181 |
"RewriteCond" :: thing
:: pat
:: rest
=>
186 |
[flags
] => if checkRewriteCondArgs (path
, flags
) then SOME flags
else NONE
187 | _
=> (Domtool
.error (path
, "Invalid mod_rewrite flags in " ^ chop line
); NONE
)
192 TextIO.output (vhosts
, "\tRewriteCond\t" ^ thing ^
" " ^ pat ^
" " ^ flags ^
"\n"))
195 |
["LocalProxy", src
, dst
, port
] =>
196 (case Int.fromString port
of
197 NONE
=> Domtool
.error (path
, "Invalid port number " ^ port
)
200 Domtool
.error (path
, "No proxying back to Apache itself allowed")
202 Domtool
.error (path
, "Port number must be positive: " ^ port
)
205 TextIO.output (vhosts
, "\tRewriteRule\t" ^ src ^
" http://localhost:" ^ port ^
"/" ^ dst ^
" [P]\n")))
208 TextIO.output (vhosts
, "\tRewriteRule\t^/cgi-bin/mailman/(.*)$ http://hcoop.net/cgi-bin/mailman/$1 [P]\n");
209 TextIO.output (vhosts
, "\tRewriteRule\t^/pipermail/(.*)$ http://hcoop.net/pipermail/$1 [P]\n"))
210 |
["Alias", from
, to
] =>
211 if checkPath (paths
, to
) then
212 TextIO.output (vhosts
, "\tAlias " ^ from ^
" " ^ to ^
"\n")
214 Domtool
.error (path
, "not authorized to use " ^ to
)
215 |
"ErrorDocument" :: code
:: rest
=>
216 TextIO.output (vhosts
, foldl (fn (a
, s
) => s ^
" " ^ a
) ("\tErrorDocument " ^ code
) rest ^
"\n")
217 |
["Script", from
, to
] =>
222 TextIO.output (vhosts
, "\tUserDir disabled\n");
223 TextIO.output (vhosts
, "\tUserDir enabled " ^
!user ^
"\n");
224 TextIO.output (vhosts
, "\t<Directory /home/" ^
!user ^
"/public_html/cgi-bin/>\n\t\tOptions ExecCGI\n\t\tSetHandler cgi-script\n\t</Directory>\n"));
226 TextIO.output (vhosts
, "\tRewriteRule\t^/" ^ from ^
"(.*)$ http://" ^ domain ^
"/~" ^
!user ^
"/cgi-bin/" ^ to ^
"$1 [P]\n"))
227 |
["MoinMoin", from
, to
] =>
232 TextIO.output (vhosts
, "\tUserDir disabled\n");
233 TextIO.output (vhosts
, "\tUserDir enabled " ^
!user ^
"\n");
234 TextIO.output (vhosts
, "\t<Directory /home/" ^
!user ^
"/public_html/cgi-bin/>\n\t\tOptions ExecCGI\n\t\tSetHandler cgi-script\n\t</Directory>\n"));
236 TextIO.output (vhosts
, "\tRewriteRule\t^/" ^ from ^
"(.*)$ http://" ^ domain ^
"/~" ^
!user ^
"/cgi-bin/" ^ to ^
"$1 [P]\n");
237 TextIO.output (vhosts
, "\tAlias /moin /usr/share/moin/htdocs\n"))
239 (*|
["ScriptAlias", from
, to
] =>
240 if checkPath (paths
, to
) then
241 TextIO.output (vhosts
, "\tScriptAlias " ^ from ^
" \"" ^ to ^
"\"\n")
243 Domtool
.error (path
, "not authorized to use " ^ to
)*)
245 TextIO.output (vhosts
, "\t<Location />\n\t\tOptions +Includes +IncludesNOEXEC\n\t</Location>\n")
246 |
["ServerAlias", dom
] =>
247 if validDomain dom
then
249 val file
= foldr (fn (c
, s
) => s ^
"/" ^ c
) dataDir (String.fields (fn ch
=> ch
= #
".") dom
) ^
".aliased"
251 if Posix
.FileSys
.access (file
, []) then
252 (TextIO.output (vhosts
, "\tServerAlias " ^ dom ^
"\n");
253 TextIO.output (conf
, "HideSite\t" ^ dom ^
"\n" ^
254 "HideReferrer\t" ^ dom ^
"\n"))
256 Domtool
.error (path
, "not authorized to ServerAlias " ^ dom
)
259 Domtool
.error (path
, "bad host: " ^ dom
)
260 |
"WebalizerUsers" :: users
=>
261 TextIO.output (htac
, "AuthType Basic\n" ^
262 "AuthName \"Abulafia web account\"\n" ^
263 "AuthUserFile " ^ passwdFile ^
"\n" ^
264 foldl (fn (u
, s
) => s ^
" " ^ u
) "Require user" users ^
"\n")
265 |
["AbuPrivate"] => TextIO.output (vhosts
,
267 "\t\tAuthName \"Abulafia web account\"\n" ^
268 "\t\tAuthType basic\n" ^
269 "\t\tAuthUserFile " ^ passwdFile ^
"\n" ^
270 "\t\tRequire valid-user\n" ^
271 "\t\tOrder Deny,Allow\n" ^
272 "\t\tDeny from all\n" ^
273 "\t\tAllow from 127.0.0.1\n" ^
274 (*"\t\tAllow from 63.246.10.45\n" ^
*)
275 "\t\tSatisfy any\n" ^
277 |
["Block", pat
] => blocked
:= pat
:: (!blocked
)
278 |
["Default"] => (TextIO.output (vhosts
, "\tServerAlias " ^ parent ^
"\n");
279 TextIO.output (conf
, "HideSite\t" ^ parent ^
"\n" ^
280 "HideReferrer\t" ^ parent ^
"\n"))
282 if checkPath (paths
, p
) then
283 TextIO.output (vhosts
, "\t<Directory " ^ p ^
">\n" ^
284 "\t\tOptions ExecCGI\n" ^
285 "\t\tSetHandler cgi-script\n" ^
288 Domtool
.error (path
, "not authorized to use " ^ p
)*)
289 |
["Mod", lang
, p
, file
] =>
290 (case List.find (fn (lang
', _
) => lang
= lang
') langHandlers
of
291 NONE
=> Domtool
.error (p
, "unknown Mod language " ^ lang
)
293 (TextIO.output (vhosts
, "\t<Location " ^ p ^
">\n");
294 TextIO.output (vhosts
, f file
);
295 TextIO.output (vhosts
, "\t</Location>\n")))
297 if checkPath (paths
, p
) then
298 TextIO.output (vhosts
, "\t<Directory " ^ p ^
">\n" ^
299 "\t\tForceType text/html\n" ^
302 Domtool
.error (path
, "not authorized to use " ^ p
)
303 |
["PerlSetVar", n
, v
] =>
304 TextIO.output (vhosts
, "\tPerlSetVar " ^ n ^
" " ^ v ^
"\n")
305 |
["AddDefaultCharset", cs
] =>
306 TextIO.output (vhosts
, "\tAddDefaultCharSet " ^ cs ^
"\n")
308 if checkPath (paths
, p
) then
310 TextIO.output (vhosts
, "\tPerlINC " ^ p ^
"\n"))
312 Domtool
.error (path
, "not authorized to use " ^ p
)
313 |
["PerlVersion", p
] =>
315 TextIO.output (vhosts
, "\tPerlVersion " ^ p ^
"\n"))
316 | cmd
::_
=> Domtool
.error (path
, "unknown option: " ^ cmd
))
318 TextIO.output (vhosts
, "<VirtualHost *>\n" ^
319 "\tServerName " ^ domain ^
"\n" ^
320 "\tErrorLog " ^ logDir ^ domain ^
"-error.log\n" ^
321 "\tCustomLog " ^ logDir ^ domain ^
"-access.log combined\n" ^
322 "\tIndexOptions FancyIndexing FoldersFirst\n");
323 ioOptLoop (fn () => Domtool
.inputLine hf
) loop ();
328 (TextIO.output (vhosts
,
330 "\t\tOrder Allow,Deny\n" ^
331 "\t\tAllow from all\n");
332 app (fn pat
=> TextIO.output (vhosts
, "\t\tDeny from " ^ pat ^
"\n")) (!blocked
);
333 TextIO.output (vhosts
, "\t</Location>\n")));
336 (TextIO.output (vhosts
, "\tSuexecUserGroup ");
337 TextIO.output (vhosts
, !user
);
338 TextIO.output (vhosts
, " ");
339 TextIO.output (vhosts
, !group
);
343 TextIO.output (vhosts
, "\tUserDir disabled\n"))
345 (TextIO.output (vhosts
, "\tUser ");
346 TextIO.output (vhosts
, !user
);
347 TextIO.output (vhosts
, "\n\tGroup ");
348 TextIO.output (vhosts
, !group
));
350 TextIO.output (vhosts
, "\n</VirtualHost>\n\n");
352 TextIO.closeOut conf
;
354 end handle ex
=> Domtool
.handleException (path
, ex
)
357 if OS
.Process
.isSuccess (OS
.Process
.system
358 (diff ^
" " ^ scratchDir ^
"/vhosts.conf " ^ dataFile
)) then
360 else if not (OS
.Process
.isSuccess (OS
.Process
.system
361 (cp ^
" " ^ scratchDir ^
"/vhosts.conf " ^ dataFile
))) then
362 (print
"Error copying vhosts.conf\n";
364 else if not (OS
.Process
.isSuccess (OS
.Process
.system pubCommand
)) then
365 (print
"Error publishing vhosts.conf\n";
367 else if OS
.Process
.isSuccess (OS
.Process
.system logpermsCommand
) then
370 (print
"Error updating log permissions\n";
373 fun mkdom _
= OS
.Process
.success
375 val _
= Domtool
.setVhostHandler
{init
= init
,