+csv dep for PG'OCaml.
[cocanwiki.git] / scripts / lib / cocanwiki_func_shell.ml
1 (* Run external shell functions.
2  * $Id: cocanwiki_func_shell.ml,v 1.1 2006/12/08 15:36:44 rich Exp $
3  *)
4
5 open Printf
6 open ExtString
7 open ExtList
8
9 open Cocanwiki_extensions
10
11 (* This exception is thrown if there is an error during processing.  The
12  * string is an error message.
13  *)
14 exception Error of string
15
16 let ws_re = Pcre.regexp "\\s+"
17
18 (* Disallowed in commands, if path is given. *)
19 let reserved = [ ".."; "/"; "\\" ]
20
21 let shell r dbh hostid arg =
22   try
23     (* Is this function enabled for this host?  This is a safety feature
24      * to ensure that people only turn this feature on if they really
25      * mean to.
26      *)
27     let rows =
28       PGSQL(dbh) "select enable_shell_func from hosts where id = $hostid" in
29     if rows <> [true] then
30       raise (Error "Shell function is disabled on this host.  You have to enable it manually by setting enable_shell_func and optionally set enable_shell_func_path for the relevant row in the hosts table.  This function is dangerous because it allows editors to run arbitrary shell commands on the server.");
31
32     let arg = match arg with
33       | None -> raise (Error ("missing command name: try {{shell:command}}"))
34       | Some arg -> arg in
35
36     let args = Pcre.split ~rex:ws_re arg in
37     let command, args =
38       match args with
39       | command :: args -> command, args
40       | _ -> raise (Error ("missing command name: try {{shell:command}}")) in
41
42     (* If the path is specified, then limit commands to run in this path.
43      * Disallow '..', '/' and '\'.
44      * If NO PATH is specified, then allow ARBITRARY commands to run (DANGER!)
45      *)
46     let path =
47       List.hd (
48         PGSQL(dbh)
49           "select enable_shell_func_path from hosts where id = $hostid"
50       ) in
51     let command =
52       match path with
53       | None -> command                 (* Any command can run. *)
54       | Some path ->
55           (* Disallow reserved characters. *)
56           if List.exists (String.exists command) reserved then
57             raise (Error "command contains reserved characters (eg. '..' or '/').");
58           Filename.concat path command in
59
60     (* Run the command and capture the output. *)
61     let cmd =
62       command ^ " " ^ String.concat " " (List.map Filename.quote args) in
63     prerr_endline cmd;
64     let chan = Unix.open_process_in cmd in
65     let lines = ref [] in
66     let lines =
67       try
68         while true; do
69           let line = input_line chan in
70           lines := line :: !lines
71         done;
72         List.rev !lines
73       with
74         End_of_file -> List.rev !lines in
75     let stat = Unix.close_process_in chan in
76     (match stat with
77      | Unix.WEXITED 0 -> ()
78      | Unix.WEXITED i ->
79          raise (Error ("command failed with code " ^ string_of_int i))
80      | Unix.WSIGNALED i ->
81          raise (Error ("command killed by signal " ^ string_of_int i))
82      | Unix.WSTOPPED i ->
83          raise (Error ("command stopped by signal " ^ string_of_int i)));
84
85     String.concat "\n" lines
86   with
87     Error msg ->
88       "shell: " ^ Cgi_escape.escape_html msg
89
90 (* Register function. *)
91 let () =
92   external_functions := ("shell", shell) :: !external_functions