(* $Id: rpc_key_service.mlp 1201 2008-08-31 23:41:22Z gerd $
 * ----------------------------------------------------------------------
 *
 *)

exception Netname_unknown
exception No_secret_key
exception Key_service_problem of exn

let () =
  Netexn.register_printer
    (Key_service_problem Not_found)
    (fun e ->
       match e with
	 | Key_service_problem e' ->
	     "Rpc_key_service.Key_service_problem(" ^ Netexn.to_string e' ^ ")"
	 | _ ->
	     assert false
    )


module A = Rpc_key_aux
module C1 = Rpc_key_clnt.KEY_PROG.KEY_VERS
module C2 = Rpc_key_clnt.KEY_PROG.KEY_VERS2
module C = C2

let program = A.program_KEY_PROG'KEY_VERS2

type client =
    [ `Direct of Rpc_client.t
    | `Keyenvoy of string
    ]

type connector =
    [ `Direct of (Rpc_client.connector * Rpc.protocol)
    | `Keyenvoy of string                    (* path of keyenvoy program *)
    ]


type t =
    { mutable client : client;
      conn : connector;
    }

let unixdomain_connector =
  `Direct (Rpc_client.Unix "/var/run/keyservsock", Rpc.Tcp) ;;

let keyenvoy_connector =
  `Keyenvoy "/usr/etc/keyenvoy" ;;

let default_connector = @CONNECTOR@_connector ;;

  (* GLIBC 2.1, FreeBSD: /var/run/keyservsock
   * Older Linux, SunOS, and others: Fork and start "/usr/etc/keyenvoy"
   * (very slow)
   * Solaris: Use TLICOTS loopback transport as reported from rpcbind
   *)

let create ?(connector = default_connector) () =
  match connector with
      `Direct (conn,prot) ->
	let c = C.create_client conn prot in
	Rpc_client.set_auth_methods c [ Rpc_auth_sys.client_auth_method() ];
	{ client = `Direct c; conn = connector }
    | `Keyenvoy path ->
	{ client = `Keyenvoy path; conn = connector }
;;


let wrap_exn f arg =
  try
    f arg
  with
      err -> raise(Key_service_problem err)
;;


let with_reconnect keyserv f =
  try
    wrap_exn f keyserv.client
  with
      Key_service_problem Rpc_client.Client_is_down ->
	let keyserv' = create ~connector:keyserv.conn () in
	keyserv.client <- keyserv'.client;
	wrap_exn f keyserv.client
;;


let call_keyenvoy keyenvoy_path procname xdr_arg  =
  let (procnumber, type_arg, type_result) =
                                      Rpc_program.signature program procname in

  let packed_arg = Xdr.pack_xdr_value_as_string xdr_arg type_arg [] in
  let (f_in, f_out) = Unix.open_process keyenvoy_path in
  let buf = String.create 1080 in     (* maximum length *)
  let pos = ref 0 in
  try
    output_string f_out (Rtypes.uint4_as_string procnumber);
    output_string f_out packed_arg;
    close_out f_out;     (* It is not an error to close files several times *)
    while true do
      let n = input f_in buf !pos (String.length buf - !pos) in
      if n = 0 then raise End_of_file;
      pos := !pos + n
    done;
    assert false
  with
      End_of_file ->
	( match Unix.close_process(f_in, f_out) with
	      Unix.WEXITED 0 -> ()
	    | _ -> failwith "call_keyenvoy"
	);
	let packed_result = String.sub buf 0 !pos in
	let xdr_result = Xdr.unpack_xdr_value ~fast:true packed_result type_result [] in
	xdr_result
    | err ->
	(ignore (Unix.close_process(f_in, f_out)));
	raise err
;;


let generate keyserv =
  with_reconnect keyserv
    (fun client ->
       match client with
	   `Direct dclient -> C.key_gen dclient ()
	 | `Keyenvoy path ->
	     A._to_des_block (call_keyenvoy path "KEY_GEN" Xdr.XV_void)
    )
;;


let encrypt keyserv remotename data =
  with_reconnect keyserv
    (fun client ->
       let arg = { A.remotename = remotename; A.deskey = data } in
       let r =
	 match client with
	     `Direct dclient ->
	       C.key_encrypt dclient arg
	   | `Keyenvoy path ->
	       A._to_cryptkeyres (call_keyenvoy path "KEY_ENCRYPT"
				    (A._of_cryptkeyarg arg))
       in
       match r with
	   `key_success encdata -> encdata
	 | `key_nosecret -> raise No_secret_key
	 | `key_unknown -> raise Netname_unknown
	 | `key_systemerr -> failwith "Rpc_key_service.encrypt"
    )
;;


let decrypt keyserv remotename encdata =
  with_reconnect keyserv
    (fun client ->
       let arg = { A.remotename = remotename; A.deskey = encdata } in
       let r =
	 match client with
	     `Direct dclient ->
	       C.key_decrypt dclient arg
	   | `Keyenvoy path ->
	       A._to_cryptkeyres (call_keyenvoy path "KEY_DECRYPT"
				    (A._of_cryptkeyarg arg))
       in
       match r with
	   `key_success data -> data
	 | `key_nosecret -> raise No_secret_key
	 | `key_unknown -> raise Netname_unknown
	 | `key_systemerr -> failwith "Rpc_key_service.decrypt"
    )
;;


let net_get keyserv =
  with_reconnect keyserv
    (fun client ->
       let r =
	 match client with
	     `Direct dclient ->
	       C.key_net_get dclient ()
	   | `Keyenvoy path ->
	       A._to_key_netstres (call_keyenvoy path "KEY_NET_GET" Xdr.XV_void)
       in
       match r with
	   `key_success { A.st_priv_key = priv;
			  A.st_pub_key = pub;
			  A.st_netname = name
			} -> (name,pub,priv)
	 | `key_nosecret -> raise No_secret_key
	 | `key_unknown -> raise Netname_unknown
	 | `key_systemerr -> failwith "Rpc_key_service.net_get"
    )
;;


let shut_down =
  wrap_exn
    (fun keyserv ->
       match keyserv.client with
	   `Direct dclient -> Rpc_client.shut_down dclient
	 | _ -> ()
    )
;;
