]> xenbits.xensource.com Git - xcp/xen-api-libs.git/commitdiff
[refactoring] Move stunnel from xen-api.hg to xen-api-libs.hg
authorDavid Scott <dave.scott@eu.citrix.com>
Mon, 26 Oct 2009 16:32:15 +0000 (16:32 +0000)
committerDavid Scott <dave.scott@eu.citrix.com>
Mon, 26 Oct 2009 16:32:15 +0000 (16:32 +0000)
Signed-off-by: Thomas Gazagnaire <thomas.gazagnaire@citrix.com>
stunnel/META.in [new file with mode: 0644]
stunnel/Makefile [new file with mode: 0644]
stunnel/stunnel.ml [new file with mode: 0644]
stunnel/stunnel.mli [new file with mode: 0644]
stunnel/stunnel_cache.ml [new file with mode: 0644]
stunnel/stunnel_cache.mli [new file with mode: 0644]

diff --git a/stunnel/META.in b/stunnel/META.in
new file mode 100644 (file)
index 0000000..5b38b05
--- /dev/null
@@ -0,0 +1,5 @@
+version = "@VERSION@"
+description = "Secure Tunneling"
+requires = "unix,stdext,log"
+archive(byte) = "stunnel.cma"
+archive(native) = "stunnel.cmxa"
diff --git a/stunnel/Makefile b/stunnel/Makefile
new file mode 100644 (file)
index 0000000..f160f1b
--- /dev/null
@@ -0,0 +1,56 @@
+CC = gcc
+CFLAGS = -Wall -fPIC -O2 -I/opt/xensource/lib/ocaml
+OCAMLC = ocamlc -g
+OCAMLOPT = ocamlopt
+
+LDFLAGS = -cclib -L./
+
+DESTDIR ?= /
+VERSION := $(shell hg parents --template "{rev}" 2>/dev/null || echo 0.0)
+OCAMLOPTFLAGS = -g -dtypes
+
+OCAMLABI := $(shell ocamlc -version)
+OCAMLLIBDIR := $(shell ocamlc -where)
+OCAMLDESTDIR ?= $(OCAMLLIBDIR)
+
+OBJS = stunnel stunnel_cache
+INTF = $(foreach obj, $(OBJS),$(obj).cmi)
+LIBS = stunnel.cma stunnel.cmxa
+
+all: $(INTF) $(LIBS) $(PROGRAMS)
+
+bins: $(PROGRAMS)
+
+libs: $(LIBS)
+
+stunnel.cmxa: $(foreach obj,$(OBJS),$(obj).cmx)
+       $(OCAMLOPT) $(OCAMLOPTFLAGS) -a -o $@ $(foreach obj,$(OBJS),$(obj).cmx)
+
+stunnel.cma: $(foreach obj,$(OBJS),$(obj).cmo)
+       $(OCAMLC) -a -o $@ $(foreach obj,$(OBJS),$(obj).cmo)
+
+%.cmo: %.ml
+       $(OCAMLC) -c -I ../stdext -I ../log -o $@ $<
+
+%.cmi: %.mli
+       $(OCAMLC) -c -o $@ $<
+
+%.cmx: %.ml
+       $(OCAMLOPT) $(OCAMLOPTFLAGS) -c -I ../stdext -I ../log -o $@ $<
+
+%.o: %.c
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+META: META.in
+       sed 's/@VERSION@/$(VERSION)/g' < $< > $@
+
+.PHONY: install
+install: $(LIBS) META
+       ocamlfind install -destdir $(DESTDIR)$(shell ocamlfind printconf destdir) -ldconf ignore stunnel META $(INTF) $(LIBS) *.a *.cmx
+
+.PHONY: uninstall
+uninstall:
+       ocamlfind remove stunnel
+
+clean:
+       rm -f *.o *.so *.a *.cmo *.cmi *.cma *.cmx *.cmxa *.annot $(LIBS) $(PROGRAMS)
\ No newline at end of file
diff --git a/stunnel/stunnel.ml b/stunnel/stunnel.ml
new file mode 100644 (file)
index 0000000..c1191d9
--- /dev/null
@@ -0,0 +1,267 @@
+(*
+ * Copyright (C) 2006-2009 Citrix Systems Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; version 2.1 only. with the special
+ * exception on linking described in file LICENSE.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *)
+(* Copyright (C) 2007 XenSource Inc *)
+
+open Printf
+open Pervasiveext
+open Stringext
+
+exception Stunnel_binary_missing
+exception Stunnel_error of string
+exception Stunnel_verify_error of string
+
+let certificate_path = "/etc/stunnel/certs"
+let crl_path = "/etc/stunnel/crls"
+
+let use_new_stunnel = ref false
+let new_stunnel_path = "/usr/sbin/stunnelng"
+
+let cached_stunnel_path = ref None
+
+let init_stunnel_path () =
+  try cached_stunnel_path := Some (Unix.getenv "XE_STUNNEL")
+  with Not_found ->
+    if !use_new_stunnel then
+      cached_stunnel_path := Some new_stunnel_path
+    else (
+      let choices = ["/opt/xensource/libexec/stunnel/stunnel";
+                    "/usr/sbin/stunnel4";
+                    "/usr/sbin/stunnel";
+                    "/usr/bin/stunnel4";
+                    "/usr/bin/stunnel";
+                   ] in
+      let rec choose l =
+        match l with
+           [] -> raise Stunnel_binary_missing
+         | (p::ps) ->
+             try Unix.access p [Unix.X_OK]; p
+             with _ -> choose ps in
+      let path = choose choices in
+      cached_stunnel_path := Some path
+    )
+
+let stunnel_path() =
+  match !cached_stunnel_path with
+    | Some p -> p 
+    | None -> raise Stunnel_binary_missing
+
+type t = { mutable pid: int; fd: Unix.file_descr; host: string; port: int; 
+          connected_time: float;
+          unique_id: int option;
+          mutable logfile: string;
+        }
+
+let config_file verify_cert extended_diagnosis host port = 
+  let lines = ["client=yes"; "foreground=yes"; "socket = r:TCP_NODELAY=1"; Printf.sprintf "connect=%s:%d" host port ] @
+    (if extended_diagnosis then
+       ["debug=4"]
+     else
+       []) @
+    (if verify_cert then
+       ["verify=2";
+        sprintf "CApath=%s" certificate_path;
+        sprintf "CRLpath=%s" crl_path]
+     else
+       [])
+  in
+    String.concat "" (List.map (fun x -> x ^ "\n") lines)
+
+let ignore_exn f x = try f x with _ -> ()
+
+let disconnect x = 
+  List.iter (ignore_exn Unix.close) [ x.fd ];
+  ignore_exn Forkhelpers.waitpid x.pid
+
+(* With some probability, stunnel fails during its startup code before it reads
+   the config data from us. Therefore we get a SIGPIPE writing the config data.
+   Assuming SIGPIPE has been ignored, catch the failing write and throw this
+   exception instead *)
+exception Stunnel_initialisation_failed
+
+let attempt_one_connect_new ?unique_id ?(use_external_fd_wrapper = true) ?(write_to_log = fun _ -> ()) verify_cert extended_diagnosis host port = 
+  assert (not verify_cert); (* !!! Unimplemented *)
+  assert (not extended_diagnosis); (* !!! Unimplemented *)
+  let data_out,data_in = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in
+  let args = [ "-m"; "client"; "-s"; "-"; "-d"; Printf.sprintf "%s:%d" host port ] in
+  let t = { pid = 0; fd = data_out; host = host; port = port; 
+           connected_time = Unix.gettimeofday (); unique_id = unique_id;
+           logfile = "" } in
+  let to_close = ref [ data_in ] in
+  let result = Forkhelpers.with_logfile_fd "stunnel" (fun logfd ->
+    let fdops = [
+      Forkhelpers.Dup2(data_in, Unix.stdin);
+      Forkhelpers.Dup2(data_in, Unix.stdout);
+      Forkhelpers.Dup2(logfd, Unix.stderr)
+    ] in
+    let fds_needed = [ Unix.stdin; Unix.stdout; Unix.stderr ] in
+    t.pid <- (
+      if use_external_fd_wrapper then
+        Forkhelpers.safe_close_and_exec fdops fds_needed (stunnel_path ()) args
+      else
+        Forkhelpers.fork_and_exec ~pre_exec:(fun _ -> 
+          List.iter Forkhelpers.do_fd_operation fdops;
+          Unixext.close_all_fds_except fds_needed
+         ) ((stunnel_path ()) :: args)
+    );
+    List.iter Unix.close [ data_in ];
+  ) in
+  List.iter Unix.close !to_close; 
+  match result with
+  | Forkhelpers.Failure(log, exn) ->
+      write_to_log ("failed: Log from stunnel: [" ^ log ^ "]");
+      disconnect t;
+      raise exn
+  | Forkhelpers.Success(log, _) -> 
+      write_to_log ("success: Log from stunnel: [" ^ log ^ "]");
+      t
+
+(* Internal function which may throw Stunnel_initialisation_failed *)
+let attempt_one_connect ?unique_id ?(use_external_fd_wrapper = true) ?(write_to_log = fun _ -> ()) verify_cert extended_diagnosis host port =
+  let data_out,data_in = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
+  and config_out, config_in = Unix.pipe ()
+  in
+  (* FDs we must close. NB stdin_in and stdout_out end up in our 't' record *)
+  let to_close = ref [ data_in; config_out; config_in  ] in
+  let close fd = 
+    if List.mem fd !to_close 
+    then (Unix.close fd; to_close := List.filter (fun x -> x <> fd) !to_close) in
+  let t = { pid = 0; fd = data_out; host = host; port = port; 
+           connected_time = Unix.gettimeofday (); unique_id = unique_id;
+           logfile = "" } in
+  let result = Forkhelpers.with_logfile_fd "stunnel"
+    ~delete:(not extended_diagnosis)
+    (fun logfd ->
+       let path = stunnel_path() in
+       let fdops = 
+        [ Forkhelpers.Dup2(data_in, Unix.stdin);
+          Forkhelpers.Dup2(data_in, Unix.stdout);
+          Forkhelpers.Dup2(logfd, Unix.stderr) ] in
+       let fds_needed = [ Unix.stdin; Unix.stdout; Unix.stderr; config_out ] in
+       let args = [ "-fd"; string_of_int (Unixext.int_of_file_descr config_out) ] in
+       if use_external_fd_wrapper then begin
+        let cmdline = Printf.sprintf "Using commandline: %s\n" (String.concat " " (Forkhelpers.close_and_exec_cmdline fds_needed path args)) in
+        write_to_log cmdline;
+       end;
+       t.pid <-
+        (if use_external_fd_wrapper
+         (* Run thread-safe external wrapper *)
+         then Forkhelpers.safe_close_and_exec fdops fds_needed path args
+         (* or do it ourselves (safe if there are no threads) *)
+         else Forkhelpers.fork_and_exec ~pre_exec:
+             (fun _ -> 
+                List.iter Forkhelpers.do_fd_operation fdops;
+                Unixext.close_all_fds_except fds_needed) 
+             (path::args) );
+       List.iter close [ data_in; config_out; ]; 
+       (* Make sure we close config_in eventually *)
+       finally
+        (fun () ->
+
+           let pidmsg = Printf.sprintf "stunnel has pid: %d\n" t.pid in
+           write_to_log pidmsg;
+
+           let config = config_file verify_cert extended_diagnosis host port in
+           (* Catch the occasional initialisation failure of stunnel: *)
+           try
+             let n = Unix.write config_in config 0 (String.length config) in
+             if n < String.length config then raise Stunnel_initialisation_failed
+           with Unix.Unix_error(err, fn, arg) -> 
+             write_to_log (Printf.sprintf "Caught Unix.Unix_error(%s, %s, %s); raising Stunnel_initialisation_failed" (Unix.error_message err) fn arg);
+             raise Stunnel_initialisation_failed)
+        (fun () -> close config_in)) in
+    (* Tidy up any remaining unclosed fds *)
+  List.iter Unix.close !to_close; 
+  match result with
+  | Forkhelpers.Success(log, _) -> 
+      if extended_diagnosis then
+        begin
+          write_to_log "success";
+          t.logfile <- log
+        end
+      else
+        write_to_log ("success: Log from stunnel: [" ^ log ^ "]");
+      t
+  | Forkhelpers.Failure(log, exn) ->
+      write_to_log ("failed: Log from stunnel: [" ^ log ^ "]");
+      disconnect t;
+      raise exn
+
+(** To cope with a slightly unreliable stunnel, attempt to retry to make 
+    the connection a number of times. *)
+let rec retry f = function
+  | 0 -> raise Stunnel_initialisation_failed
+  | n -> 
+      try f ()
+      with Stunnel_initialisation_failed -> 
+       (* Leave a few seconds between each attempt *)
+       ignore(Unix.select [] [] [] 3.);
+       retry f (n - 1)
+
+(** Establish a fresh stunnel to a (host, port)
+    @param extended_diagnosis If true, the stunnel log file will not be
+    deleted.  Instead, it is the caller's responsibility to delete it.  This
+    allows the caller to use diagnose_failure below if stunnel fails.  *)
+let connect ?unique_id ?use_external_fd_wrapper ?write_to_log
+    ?(verify_cert=false) ?(extended_diagnosis=false) host port = 
+  let connect = if !use_new_stunnel then attempt_one_connect_new else attempt_one_connect in
+  retry (fun () -> connect ?unique_id ?use_external_fd_wrapper ?write_to_log verify_cert extended_diagnosis host port) 5
+
+let sub_after i s =
+  let len = String.length s in
+    String.sub s i (len - i)
+
+let split_1 c s =
+  match String.split ~limit:1 c s with
+    | x :: _ -> x
+    | [] -> s
+
+let check_verify_error line =
+  match String.find_all "VERIFY ERROR: " line with
+      | p :: _ ->
+          begin
+            match String.find_all "error=" line with
+              | e :: _ ->
+                  raise
+                    (Stunnel_verify_error
+                       (split_1 ','
+                          (sub_after (e + String.length "error=") line)))
+              | [] ->
+                  raise (Stunnel_verify_error "")
+          end
+      | [] ->
+          ()
+          
+let check_error s line =
+  if (String.has_substr line s) then
+    raise (Stunnel_error s)
+    
+let diagnose_failure st_proc =
+  let check_line line =
+    Printf.eprintf "stunnel_failure: %s\n" line;
+    check_verify_error line;
+    check_error "Connection refused" line; 
+    check_error "No host resolved" line;
+    check_error "Invalid argument" line;
+  in
+    Unixext.readfile_line check_line st_proc.logfile;
+    raise (Stunnel_error (Unixext.read_whole_file_to_string st_proc.logfile))
+
+let test host port = 
+  let counter = ref 0 in
+  while true do
+    let c = connect ~write_to_log:print_endline host port in
+    disconnect c;
+    incr counter;
+    if !counter mod 100 = 0 then (Printf.printf "Ran stunnel %d times\n" !counter; flush stdout)
+  done
diff --git a/stunnel/stunnel.mli b/stunnel/stunnel.mli
new file mode 100644 (file)
index 0000000..4251bc6
--- /dev/null
@@ -0,0 +1,53 @@
+(*
+ * Copyright (C) 2006-2009 Citrix Systems Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; version 2.1 only. with the special
+ * exception on linking described in file LICENSE.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *)
+(** Thrown if we can't find the stunnel binary in the prescribed location *)
+exception Stunnel_binary_missing
+exception Stunnel_error of string
+exception Stunnel_verify_error of string
+
+val certificate_path : string
+val crl_path : string
+
+val use_new_stunnel : bool ref
+val init_stunnel_path : unit -> unit
+
+(** Represents an active stunnel connection *)
+type t = { mutable pid: int; 
+          fd: Unix.file_descr; 
+          host: string; 
+          port: int;
+          connected_time: float; (** time when the connection opened, for 'early retirement' *)
+          unique_id: int option;
+          mutable logfile: string;
+        }
+
+(** Connects via stunnel (optionally via an external 'close fds' wrapper) to
+    a host and port.
+    NOTE: this does not guarantee the connection to the remote server actually works.
+    For server-side connections, use Xmlrpcclient.get_reusable_stunnel instead.
+ *)
+val connect :
+  ?unique_id:int ->
+  ?use_external_fd_wrapper:bool ->
+  ?write_to_log:(string -> unit) ->
+  ?verify_cert:bool ->
+  ?extended_diagnosis:bool ->
+  string -> int -> t
+
+(** Disconnects from stunnel and cleans up *)
+val disconnect : t -> unit
+
+val diagnose_failure : t -> unit
+
+val test : string -> int -> unit
diff --git a/stunnel/stunnel_cache.ml b/stunnel/stunnel_cache.ml
new file mode 100644 (file)
index 0000000..feb6dac
--- /dev/null
@@ -0,0 +1,176 @@
+(*
+ * Copyright (C) 2006-2009 Citrix Systems Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; version 2.1 only. with the special
+ * exception on linking described in file LICENSE.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *)
+
+(** Operate a small cache of stunnels so we can re-use them for repeated calls *)
+
+(* Caveats:
+   * stunnel donators should only donate stunnels which they knows are connected
+     to the main HTTP request loop in the server -- HTTP 1.1 should be used and 
+     the connection should be kept-alive
+ *)
+
+module D=Debug.Debugger(struct let name="stunnel_cache" end)
+open D
+
+type endpoint = { host: string; port: int }
+
+(** An index of endpoints to stunnel IDs *)
+let index : (endpoint, int list) Hashtbl.t ref = ref (Hashtbl.create 10)
+(** A mapping of stunnel unique IDs to donation times *)
+let times : (int, float) Hashtbl.t ref = ref (Hashtbl.create 10)
+(** A mapping of stunnel unique ID to Stunnel.t *)
+let stunnels : (int, Stunnel.t) Hashtbl.t ref = ref (Hashtbl.create 10)
+
+open Pervasiveext
+open Threadext
+open Listext
+
+let m = Mutex.create ()
+
+(* Need to limit the absolute number of stunnels as well as the maximum age *)
+let max_stunnel = 4
+let max_age = 5. *. 60. (* seconds *)
+let max_idle = 2. *. 60. (* seconds *)
+
+let id_of_stunnel stunnel = 
+    Opt.default "unknown" (Opt.map string_of_int stunnel.Stunnel.unique_id)
+
+let unlocked_gc () = 
+  let now = Unix.gettimeofday () in
+  let string_of_id id = 
+    let stunnel = Hashtbl.find !stunnels id in
+    Printf.sprintf "(id %s / idle %.2f age %.2f)" 
+      (id_of_stunnel stunnel)
+      (now -. (Hashtbl.find !times id))
+      (now -. stunnel.Stunnel.connected_time) in
+  let string_of_endpoint ep = Printf.sprintf "%s:%d" ep.host ep.port in
+  let string_of_index ep xs = Printf.sprintf "[ %s %s ]" (string_of_endpoint ep) (String.concat "; " (List.map string_of_id xs)) in
+  debug "Cache contents: %s" (Hashtbl.fold (fun ep xs acc -> string_of_index ep xs ^ " " ^ acc) !index "");
+
+  let all_ids = Hashtbl.fold (fun k _ acc -> k :: acc) !stunnels [] in
+
+  let to_gc = ref [] in
+  (* Find the ones which are too old *)
+  let now = Unix.gettimeofday () in
+  Hashtbl.iter
+    (fun idx stunnel ->
+       let time = Hashtbl.find !times idx in
+       let idle = now -. time in
+       let age = now -. stunnel.Stunnel.connected_time in
+       if age > max_age then begin
+        debug "Expiring stunnel id %s; age (%.2f) > limit (%.2f)" (id_of_stunnel stunnel) age max_age;
+        to_gc := idx :: !to_gc
+       end else if idle > max_idle then begin
+        debug "Expiring stunnel id %s; idle (%.2f) > limit (%.2f)" (id_of_stunnel stunnel) age max_idle;
+        to_gc := idx :: !to_gc
+       end) !stunnels;
+  let num_remaining = List.length all_ids - (List.length !to_gc) in
+  if num_remaining > max_stunnel then begin
+    let times' = Hashtbl.fold (fun k v acc -> (k, v) :: acc) !times [] in
+    let times' = List.filter (fun (idx, _) -> not(List.mem idx !to_gc)) times' in
+    (* Sort into descending order of donation time, ie youngest first *)
+    let times' = List.sort (fun x y -> compare (fst y) (fst x)) times' in
+    let youngest, oldest = List.chop max_stunnel times' in
+    let oldest_ids = List.map fst oldest in
+    List.iter
+      (fun x -> 
+        let stunnel = Hashtbl.find !stunnels x in
+        debug "Expiring stunnel id %s since we have too many cached tunnels (limit is %d)" 
+          (id_of_stunnel stunnel) max_stunnel) oldest_ids;
+    to_gc := !to_gc @ oldest_ids
+  end;
+  (* Disconnect all stunnels we wish to GC *)
+  List.iter (fun id ->
+              let s = Hashtbl.find !stunnels id in
+              Stunnel.disconnect s) !to_gc;
+  (* Remove all reference to them from our cache hashtables *)
+  let index' = Hashtbl.create 10 in
+  Hashtbl.iter
+    (fun ep ids ->
+       Hashtbl.add index' ep (List.filter (fun id -> not(List.mem id !to_gc)) ids)) !index;
+  let times' = Hashtbl.copy !times in
+  List.iter (fun idx -> Hashtbl.remove times' idx) !to_gc;
+  let stunnels' = Hashtbl.copy !stunnels in
+  List.iter (fun idx -> Hashtbl.remove stunnels' idx) !to_gc;
+
+  index := index';
+  times := times';
+  stunnels := stunnels'
+
+let gc () = Mutex.execute m unlocked_gc
+
+let counter = ref 0
+
+let add (x: Stunnel.t) = 
+  let now = Unix.gettimeofday () in
+  Mutex.execute m
+    (fun () ->
+       let idx = !counter in
+       incr counter;
+       Hashtbl.add !times idx now;
+       Hashtbl.add !stunnels idx x;
+       let ep = { host = x.Stunnel.host; port = x.Stunnel.port } in
+       let existing = 
+        if Hashtbl.mem !index ep
+        then Hashtbl.find !index ep
+        else [] in
+       Hashtbl.replace !index ep (idx :: existing);
+       debug "Adding stunnel id %s (idle %.2f) to the cache"
+            (id_of_stunnel x) 0.;
+       unlocked_gc ()
+    )
+  
+(** Returns an Stunnel.t for this endpoint (oldest first), raising Not_found
+    if none can be found *)
+let remove host port = 
+  let ep = { host = host; port = port } in
+  Mutex.execute m
+    (fun () ->
+       unlocked_gc ();
+
+       let ids = Hashtbl.find !index ep in
+       let table = List.map (fun id -> id, Hashtbl.find !times id) ids in
+       let sorted = List.sort (fun a b -> compare (snd a) (snd b)) table in
+       match sorted with
+       | (id, time) :: _ ->
+          let stunnel = Hashtbl.find !stunnels id in
+          debug "Removing stunnel id %s (idle %.2f) from the cache"
+            (id_of_stunnel stunnel) (Unix.gettimeofday () -. time);
+          let stunnel = Hashtbl.find !stunnels id in
+          Hashtbl.remove !stunnels id;
+          Hashtbl.remove !times id;
+          Hashtbl.replace !index ep (List.filter (fun x -> x <> id) ids);
+          stunnel
+       | _ -> raise Not_found
+    )
+
+(** Flush the cache - remove everything *)
+let flush () =
+  Mutex.execute m 
+    (fun () ->
+      info "Flushing cache";
+      Hashtbl.iter (fun id st -> Stunnel.disconnect st) !stunnels;
+      Hashtbl.clear !stunnels;
+      Hashtbl.clear !times;
+      Hashtbl.clear !index;
+      info "Flushed!")
+
+
+let connect ?use_external_fd_wrapper ?write_to_log host port = 
+  try
+    remove host port
+  with Not_found ->
+    error "Failed to find stunnel in cache for endpoint %s:%d" host port;
+    Stunnel.connect ?use_external_fd_wrapper ?write_to_log host port
+    
diff --git a/stunnel/stunnel_cache.mli b/stunnel/stunnel_cache.mli
new file mode 100644 (file)
index 0000000..bb8cb57
--- /dev/null
@@ -0,0 +1,41 @@
+(*
+ * Copyright (C) 2006-2009 Citrix Systems Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; version 2.1 only. with the special
+ * exception on linking described in file LICENSE.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *)
+
+(** Operate a small cache of stunnels so we can re-use them for repeated calls.
+
+ Caveats:
+   * stunnel donators should only donate stunnels which they knows are connected
+     to the main HTTP request loop in the server -- HTTP 1.1 should be used and 
+     the connection should be kept-alive.
+*)
+
+
+(** Connects via stunnel (optionally via an external 'close fds' wrapper) to
+    a host and port. If there is a suitable stunnel in the cache then this 
+    will be used, otherwise we make a fresh one. *)
+val connect :
+  ?use_external_fd_wrapper:bool ->
+  ?write_to_log:(string -> unit) -> string -> int -> Stunnel.t
+
+(** Adds a reusable stunnel to the cache *)
+val add : Stunnel.t -> unit
+
+(** Given a host and port return a cached stunnel, or throw Not_found *)
+val remove : string -> int -> Stunnel.t
+
+(** Empty the cache of all stunnels *)
+val flush : unit -> unit
+
+(** GCs old stunnels *)
+val gc : unit -> unit