Added {{shell}} for running external shell commands.
authorrich <rich>
Fri, 8 Dec 2006 15:36:44 +0000 (15:36 +0000)
committerrich <rich>
Fri, 8 Dec 2006 15:36:44 +0000 (15:36 +0000)
scripts/Makefile
scripts/lib/cocanwiki_func_shell.ml [new file with mode: 0644]

index f0a1692..9885b02 100644 (file)
@@ -1,5 +1,5 @@
 # Makefile for COCANWIKI.
-# $Id: Makefile,v 1.53 2006/12/07 17:05:47 rich Exp $
+# $Id: Makefile,v 1.54 2006/12/08 15:36:44 rich Exp $
 
 include ../Makefile.config
 
@@ -52,7 +52,8 @@ LIB_OBJS := \
        lib/cocanwiki_pages.cmo \
        lib/cocanwiki_diff.cmo \
        lib/cocanwiki_mail.cmo \
-       lib/cdvmm_phone_numbers.cmo
+       lib/cdvmm_phone_numbers.cmo \
+       lib/cocanwiki_func_shell.cmo
 
 ifneq ($(OCAMLRSS),)
 LIB_OBJS += lib/cocanwiki_func_rss.cmo
diff --git a/scripts/lib/cocanwiki_func_shell.ml b/scripts/lib/cocanwiki_func_shell.ml
new file mode 100644 (file)
index 0000000..6c708c4
--- /dev/null
@@ -0,0 +1,92 @@
+(* Run external shell functions.
+ * $Id: cocanwiki_func_shell.ml,v 1.1 2006/12/08 15:36:44 rich Exp $
+ *)
+
+open Printf
+open ExtString
+open ExtList
+
+open Cocanwiki_extensions
+
+(* This exception is thrown if there is an error during processing.  The
+ * string is an error message.
+ *)
+exception Error of string
+
+let ws_re = Pcre.regexp "\\s+"
+
+(* Disallowed in commands, if path is given. *)
+let reserved = [ ".."; "/"; "\\" ]
+
+let shell r dbh hostid arg =
+  try
+    (* Is this function enabled for this host?  This is a safety feature
+     * to ensure that people only turn this feature on if they really
+     * mean to.
+     *)
+    let rows =
+      PGSQL(dbh) "select enable_shell_func from hosts where id = $hostid" in
+    if rows <> [true] then
+      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.");
+
+    let arg = match arg with
+      | None -> raise (Error ("missing command name: try {{shell:command}}"))
+      | Some arg -> arg in
+
+    let args = Pcre.split ~rex:ws_re arg in
+    let command, args =
+      match args with
+      | command :: args -> command, args
+      | _ -> raise (Error ("missing command name: try {{shell:command}}")) in
+
+    (* If the path is specified, then limit commands to run in this path.
+     * Disallow '..', '/' and '\'.
+     * If NO PATH is specified, then allow ARBITRARY commands to run (DANGER!)
+     *)
+    let path =
+      List.hd (
+       PGSQL(dbh)
+         "select enable_shell_func_path from hosts where id = $hostid"
+      ) in
+    let command =
+      match path with
+      | None -> command                        (* Any command can run. *)
+      | Some path ->
+         (* Disallow reserved characters. *)
+         if List.exists (String.exists command) reserved then
+           raise (Error "command contains reserved characters (eg. '..' or '/').");
+         Filename.concat path command in
+
+    (* Run the command and capture the output. *)
+    let cmd =
+      command ^ " " ^ String.concat " " (List.map Filename.quote args) in
+    prerr_endline cmd;
+    let chan = Unix.open_process_in cmd in
+    let lines = ref [] in
+    let lines =
+      try
+        while true; do
+         let line = input_line chan in
+         lines := line :: !lines
+       done;
+       List.rev !lines
+      with
+       End_of_file -> List.rev !lines in
+    let stat = Unix.close_process_in chan in
+    (match stat with
+     | Unix.WEXITED 0 -> ()
+     | Unix.WEXITED i ->
+        raise (Error ("command failed with code " ^ string_of_int i))
+     | Unix.WSIGNALED i ->
+        raise (Error ("command killed by signal " ^ string_of_int i))
+     | Unix.WSTOPPED i ->
+        raise (Error ("command stopped by signal " ^ string_of_int i)));
+
+    String.concat "\n" lines
+  with
+    Error msg ->
+      "shell: " ^ Cgi_escape.escape_html msg
+
+(* Register function. *)
+let () =
+  external_functions := ("shell", shell) :: !external_functions