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 | (* Main domtool structure *) | |
21 | ||
22 | structure Domtool = | |
23 | struct | |
24 | open Config Util | |
25 | ||
26 | val uid = Posix.ProcEnv.wordToUid (SysWord.fromInt uid) | |
27 | ||
28 | val dprint = if debug then (fn x => print (x ^ "\n")) else (fn _ => ()) | |
29 | ||
30 | fun openIn fname = | |
31 | ((*dprint ("Open " ^ fname ^ "....");*) | |
32 | TextIO.openIn fname) | |
33 | ||
34 | fun opendir fname = | |
35 | ((*dprint ("Open dir " ^ fname ^ "....");*) | |
36 | Posix.FileSys.opendir fname) | |
37 | ||
38 | fun inputLine fl = | |
39 | let | |
40 | val s = TextIO.inputLine fl | |
41 | in | |
42 | if size s = 0 orelse String.sub (s, 0) <> #"#" then | |
43 | s | |
44 | else | |
45 | inputLine fl | |
46 | end | |
47 | ||
48 | fun hd' ([], x) = x | |
49 | | hd' (h::_, _) = h | |
50 | ||
51 | fun domPrep (a, b) = | |
52 | (case b of | |
53 | "" => a | |
54 | | _ => a ^ "." ^ b) | |
55 | ||
56 | ||
57 | type map = string StringMap.map | |
58 | type set = StringSet.set | |
59 | ||
60 | type handlerData = {path : string, | |
61 | domain : string, | |
62 | parent : string, | |
63 | vars : map, | |
64 | paths : set, | |
65 | users : set, | |
66 | groups : set} | |
67 | type mkdomData = {path : string, | |
68 | domain : string} | |
69 | type handler = {init : unit -> unit, | |
70 | file : handlerData -> unit, | |
71 | finish : unit -> unit, | |
72 | publish : unit -> OS.Process.status, | |
73 | mkdom : mkdomData -> OS.Process.status} | |
74 | ||
75 | val nullHandler = {init = fn () => (), | |
76 | file = fn (_ : handlerData) => (), | |
77 | finish = fn () => (), | |
78 | publish = fn () => OS.Process.success, | |
79 | mkdom = fn (_ : mkdomData) => OS.Process.success} | |
80 | ||
81 | val vhostHandler = ref nullHandler | |
82 | fun setVhostHandler f = vhostHandler := f | |
83 | ||
84 | val handlers = ref (StringMap.empty : handler StringMap.map) | |
85 | fun setHandler (fname, handler) = handlers := StringMap.insert (!handlers, fname, handler) | |
86 | ||
87 | fun error (path, msg) = print (path ^ ": " ^ msg ^ "\n") | |
88 | ||
89 | fun read () = | |
90 | let | |
91 | val vhostHandler = !vhostHandler | |
92 | ||
93 | val _ = Posix.FileSys.mkdir (lockFile, Posix.FileSys.S.irwxu) | |
94 | ||
95 | fun readDir (path, prefix, vars, paths, users, groups) = | |
96 | let | |
97 | val _ = dprint ("readDir: " ^ path) | |
98 | val dir = opendir path | |
99 | ||
100 | val vars = | |
101 | if Posix.FileSys.access (path ^ "/.vars", []) then | |
102 | let | |
103 | val vf = openIn (path ^ "/.vars") | |
104 | ||
105 | fun loop vars = | |
106 | let | |
107 | val line = inputLine vf | |
108 | in | |
109 | case line of | |
110 | "" => vars | |
111 | | _ => | |
112 | (case String.tokens Char.isSpace line of | |
113 | [n, v] => | |
114 | if validHost n then | |
115 | loop (StringMap.insert (vars, n, v)) | |
116 | else | |
117 | (error (path ^ "/.vars", "Invalid variable declaration: " ^ | |
118 | String.substring (line, 0, size line - 1)); | |
119 | loop vars) | |
120 | | _ => loop vars) | |
121 | end | |
122 | in | |
123 | loop vars | |
124 | before TextIO.closeIn vf | |
125 | end | |
126 | else | |
127 | vars | |
128 | ||
129 | val pp = path ^ "/.paths" | |
130 | val paths = | |
131 | if Posix.FileSys.access (pp, []) then | |
132 | (if Posix.FileSys.ST.uid (Posix.FileSys.stat pp) = uid then | |
133 | let | |
134 | val vf = openIn pp | |
135 | ||
136 | fun loop paths = | |
137 | let | |
138 | val line = inputLine vf | |
139 | in | |
140 | case line of | |
141 | "" => paths | |
142 | | _ => | |
143 | (case String.tokens Char.isSpace line of | |
144 | [path] => loop (StringSet.add (paths, path)) | |
145 | | _ => loop paths) | |
146 | end | |
147 | in | |
148 | loop paths | |
149 | before TextIO.closeIn vf | |
150 | end | |
151 | else | |
152 | (error (pp, "wrong owner to be used"); | |
153 | paths)) | |
154 | else | |
155 | paths | |
156 | ||
157 | val up = path ^ "/.users" | |
158 | val users = | |
159 | if Posix.FileSys.access (up, []) then | |
160 | (if Posix.FileSys.ST.uid (Posix.FileSys.stat up) = uid then | |
161 | let | |
162 | val vf = openIn up | |
163 | ||
164 | fun loop users = | |
165 | let | |
166 | val line = inputLine vf | |
167 | in | |
168 | case line of | |
169 | "" => users | |
170 | | _ => | |
171 | (case String.tokens Char.isSpace line of | |
172 | [user] => loop (StringSet.add (users, user)) | |
173 | | _ => loop users) | |
174 | end | |
175 | in | |
176 | loop users | |
177 | before TextIO.closeIn vf | |
178 | end | |
179 | else | |
180 | (error (up, ": wrong owner to be used."); | |
181 | users)) | |
182 | else | |
183 | users | |
184 | ||
185 | val gp = path ^ "/.groups" | |
186 | val groups = | |
187 | if Posix.FileSys.access (gp, []) then | |
188 | (if Posix.FileSys.ST.uid (Posix.FileSys.stat gp) = uid then | |
189 | let | |
190 | val vf = openIn gp | |
191 | ||
192 | fun loop groups = | |
193 | let | |
194 | val line = inputLine vf | |
195 | in | |
196 | case line of | |
197 | "" => groups | |
198 | | _ => | |
199 | (case String.tokens Char.isSpace line of | |
200 | [group] => loop (StringSet.add (groups, group)) | |
201 | | _ => loop groups) | |
202 | end | |
203 | in | |
204 | loop groups | |
205 | before TextIO.closeIn vf | |
206 | end | |
207 | else | |
208 | (error (gp, "wrong owner to be used."); | |
209 | groups)) | |
210 | else | |
211 | groups | |
212 | ||
213 | fun loop (name, ()) = | |
214 | let | |
215 | val path' = path ^ "/" ^ name | |
216 | val prefix' = domPrep (name, prefix) | |
217 | in | |
218 | if not (Posix.FileSys.access (path', [])) then | |
219 | () | |
220 | else if Posix.FileSys.ST.isDir (Posix.FileSys.stat path') then | |
221 | readDir (path', prefix', vars, paths, users, groups) | |
222 | else if isTmp name then | |
223 | () | |
224 | else case StringMap.find (!handlers, name) of | |
225 | NONE => if validHost name andalso path' <> defaultWebPath then | |
226 | #file vhostHandler {path = path', | |
227 | domain = prefix', | |
228 | parent = prefix, | |
229 | vars = vars, | |
230 | paths = paths, | |
231 | users = users, | |
232 | groups = groups} | |
233 | else | |
234 | () | |
235 | | SOME {file, ...} => file {path = path', | |
236 | domain = prefix', | |
237 | parent = prefix, | |
238 | vars = vars, | |
239 | paths = paths, | |
240 | users = users, | |
241 | groups = groups} | |
242 | end | |
243 | in | |
244 | ioOptLoop (fn () => Posix.FileSys.readdir dir) loop (); | |
245 | Posix.FileSys.closedir dir | |
246 | end handle Io => error (path, "IO error") | |
247 | in | |
248 | #init vhostHandler (); | |
249 | StringMap.app (fn {init, ...} => init ()) (!handlers); | |
250 | readDir (dataDir, "", StringMap.empty, | |
251 | StringSet.empty, StringSet.empty, StringSet.empty); | |
252 | StringMap.app (fn {finish, ...} => finish ()) (!handlers); | |
253 | #finish vhostHandler (); | |
254 | Posix.FileSys.rmdir lockFile; | |
255 | print "Processing complete.\n"; | |
256 | OS.Process.system dompub | |
257 | end handle Io => (print "IO error. Is domtool already running?\n"; | |
258 | OS.Process.failure) | |
259 | ||
260 | fun combineStatus (a, b) = | |
261 | if OS.Process.isSuccess a andalso OS.Process.isSuccess b then | |
262 | OS.Process.success | |
263 | else | |
264 | OS.Process.failure | |
265 | ||
266 | fun publish () = | |
267 | StringMap.foldl (fn ({publish, ...}, status) => | |
268 | combineStatus (status, publish ())) | |
269 | (#publish (!vhostHandler) ()) | |
270 | (!handlers) | |
271 | ||
272 | fun mkdir dir = | |
273 | let | |
274 | fun mkd (tokens, acc) = | |
275 | case tokens of | |
276 | [] => true | |
277 | | (dir::rest) => | |
278 | let | |
279 | val dir = acc ^ "/" ^ dir | |
280 | in | |
281 | if not (Posix.FileSys.access (dir, [])) | |
282 | andalso OS.Process.system ("mkdir " ^ dir) = OS.Process.failure then | |
283 | (print ("Can't created directory " ^ dir ^ "\n"); | |
284 | false) | |
285 | else | |
286 | mkd (rest, dir) | |
287 | end | |
288 | in | |
289 | mkd (String.tokens (fn ch => ch = #"/") dir, dataDir) | |
290 | end | |
291 | ||
292 | fun mkdom (dom, user) = | |
293 | if not (validDomain dom) then | |
294 | (print "Invalid domain\n"; | |
295 | OS.Process.failure) | |
296 | else if not (validUser user) then | |
297 | (print "Invalid user\n"; | |
298 | OS.Process.failure) | |
299 | else | |
300 | let | |
301 | val dir' = toDir dom | |
302 | val dir = dataDir ^ dir' | |
303 | in | |
304 | if not (mkdir dir') | |
305 | orelse OS.Process.system ("echo " ^ user ^ " >" ^ dir ^ "/.users") = OS.Process.failure | |
306 | orelse OS.Process.system ("echo " ^ user ^ " >" ^ dir ^ "/.groups") = OS.Process.failure | |
307 | orelse OS.Process.system ("echo /home/" ^ user ^ " >" ^ dir ^ "/.paths") = OS.Process.failure | |
308 | orelse OS.Process.system ("chown -R " ^ user ^ "." ^ user ^ " " ^ dir) = OS.Process.failure | |
309 | orelse OS.Process.system ("chmod -R g+w " ^ dir) = OS.Process.failure | |
310 | orelse OS.Process.system ("chown domains.adm " ^ dir ^ "/.users " ^ dir ^ "/.groups " ^ dir ^ "/.paths") = OS.Process.failure then | |
311 | (print "Setup failed\n"; | |
312 | OS.Process.failure) | |
313 | else if not (OS.Process.isSuccess | |
314 | (StringMap.foldl (fn ({mkdom, ...}, status) => | |
315 | combineStatus (status, mkdom {path = dir, domain = dom})) | |
316 | (#mkdom (!vhostHandler) {path = dir, domain = dom}) | |
317 | (!handlers))) then | |
318 | (print "Setup failed\n"; | |
319 | OS.Process.failure) | |
320 | else | |
321 | (print "Domain created\n"; | |
322 | OS.Process.success) | |
323 | end | |
324 | end |